Working on auth...
This commit is contained in:
parent
7a5f83c633
commit
f0755615ae
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/config.json
|
|
@ -9,10 +9,10 @@ interface TableSpec {
|
||||||
prepStatements?: string[];
|
prepStatements?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamps = [
|
const createdAtTimestamp = "created_at timestamptz not null default now()";
|
||||||
"created_at timestamptz not null default now()",
|
const updatedAtTimestamp = "updated_at timestamptz not null default now()";
|
||||||
"updated_at timestamptz not null default now()",
|
|
||||||
];
|
const timestamps = [createdAtTimestamp, updatedAtTimestamp];
|
||||||
|
|
||||||
const tables: Record<string, TableSpec> = {
|
const tables: Record<string, TableSpec> = {
|
||||||
"note": {
|
"note": {
|
||||||
|
@ -26,15 +26,30 @@ const tables: Record<string, TableSpec> = {
|
||||||
columns: [
|
columns: [
|
||||||
id,
|
id,
|
||||||
"username text not null unique",
|
"username text not null unique",
|
||||||
"hashed_password text not null",
|
"password_digest text not null",
|
||||||
"status user_status not null",
|
"status user_status not null",
|
||||||
"display_name text",
|
"display_name text",
|
||||||
...timestamps,
|
...timestamps,
|
||||||
],
|
],
|
||||||
|
additionalTableStatements: [
|
||||||
|
"constraint valid_username check (username ~* '^[a-z\d\\-_]{2,38}$')",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"user_token": {
|
"user_token": {
|
||||||
|
prepStatements: [
|
||||||
|
"drop type if exists user_token_type",
|
||||||
|
"create type user_token_type as enum ('session', 'reset')",
|
||||||
|
],
|
||||||
columns: [
|
columns: [
|
||||||
id,
|
id,
|
||||||
|
"token_digest bytea not null unique",
|
||||||
|
"type user_token_type not null",
|
||||||
|
"sent_to text not null",
|
||||||
|
createdAtTimestamp,
|
||||||
|
],
|
||||||
|
additionalStatements: [
|
||||||
|
"create index team_user_type on user_token (type)",
|
||||||
|
"create index team_user_sent_to on user_token (sent_to)",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"team": {
|
"team": {
|
||||||
|
@ -94,8 +109,20 @@ create extension if not exists "uuid-ossp";
|
||||||
|
|
||||||
${createTables}
|
${createTables}
|
||||||
|
|
||||||
|
commit;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const setupResult = await query(queryString);
|
||||||
|
|
||||||
|
console.debug(setupResult);
|
||||||
|
console.log(queryString);
|
||||||
|
|
||||||
|
const seedQuery = `
|
||||||
|
|
||||||
|
-- TODO: create reserved usernames?
|
||||||
|
|
||||||
with new_user as (
|
with new_user as (
|
||||||
insert into "user" (username, hashed_password, status)
|
insert into "user" (username, password_digest, status)
|
||||||
values ('lytedev', '$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW', 'superadmin')
|
values ('lytedev', '$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW', 'superadmin')
|
||||||
returning id as user_id
|
returning id as user_id
|
||||||
), new_team as (
|
), new_team as (
|
||||||
|
@ -108,14 +135,11 @@ insert into "team_user" (user_id, team_id, status)
|
||||||
(select user_id from new_user),
|
(select user_id from new_user),
|
||||||
(select team_id from new_team),
|
(select team_id from new_team),
|
||||||
'owner'
|
'owner'
|
||||||
);
|
) returning user_id;
|
||||||
|
|
||||||
commit;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log(queryString);
|
const seedResult = await query(seedQuery);
|
||||||
|
|
||||||
const result = await query(queryString);
|
console.debug(seedResult);
|
||||||
|
console.log(seedQuery);
|
||||||
console.debug(result);
|
|
||||||
console.log(queryString);
|
|
||||||
|
|
39
mail/mod.ts
Normal file
39
mail/mod.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import Mailgun from "https://deno.land/x/mailgun@v1.1.1/index.ts";
|
||||||
|
import { type Message } from "https://deno.land/x/mailgun@v1.1.1/types.ts";
|
||||||
|
|
||||||
|
export type Email = Message;
|
||||||
|
|
||||||
|
const MAILGUN_API_KEY = Deno.env.get("MAILGUN_API_KEY");
|
||||||
|
const MAILGUN_DOMAIN = Deno.env.get("MAILGUN_DOMAIN");
|
||||||
|
|
||||||
|
if (!MAILGUN_API_KEY) {
|
||||||
|
console.error(
|
||||||
|
"MAILGUN_API_KEY not set. Emails will not be sent, only logged to the console.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MAILGUN_DOMAIN) {
|
||||||
|
console.error(
|
||||||
|
"MAILGUN_API_KEY not set. Emails will not be sent, only logged to the console.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailgun = new Mailgun({
|
||||||
|
key: "YOUR_API_KEY",
|
||||||
|
region: "us",
|
||||||
|
domain: "YOUR_DOMAIN",
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function send(email: Email) {
|
||||||
|
console.debug("Email:", email);
|
||||||
|
if (MAILGUN_API_KEY && MAILGUN_DOMAIN) {
|
||||||
|
return await mailgun.send(email);
|
||||||
|
} else {
|
||||||
|
return new Response(
|
||||||
|
"Email client is not properly configured. Email not sent.",
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ export const handler: Handlers<UserID | LoginError | null> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await query<
|
const result = await query<
|
||||||
{ id: string; username: string; hashed_password: string }
|
{ id: string; username: string; password_digest: string }
|
||||||
>(
|
>(
|
||||||
`select * from "user" where username = $1`,
|
`select * from "user" where username = $1`,
|
||||||
[username],
|
[username],
|
||||||
|
@ -34,8 +34,8 @@ export const handler: Handlers<UserID | LoginError | null> = {
|
||||||
if (result == null || result.rows.length < 1) {
|
if (result == null || result.rows.length < 1) {
|
||||||
return await invalidLogin(context);
|
return await invalidLogin(context);
|
||||||
}
|
}
|
||||||
const { rows: [{ id, hashed_password }] } = result;
|
const { rows: [{ id, password_digest }] } = result;
|
||||||
if (await compare(password.toString(), hashed_password)) {
|
if (await compare(password.toString(), password_digest)) {
|
||||||
return await context.render(id);
|
return await context.render(id);
|
||||||
} else {
|
} else {
|
||||||
return await invalidLogin(context);
|
return await invalidLogin(context);
|
||||||
|
|
|
@ -21,13 +21,13 @@ export const handler: Handlers<UserID | RegistrationError | null> = {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
return await context.render({ message: "no password provided" });
|
return await context.render({ message: "no password provided" });
|
||||||
}
|
}
|
||||||
const hashed_password = await hash(password.toString());
|
const password_digest = await hash(password.toString());
|
||||||
try {
|
try {
|
||||||
const result = await query<{ user_id: string }>(
|
const result = await query<{ user_id: string }>(
|
||||||
`
|
`
|
||||||
with new_user as (
|
with new_user as (
|
||||||
insert into "user" (username, hashed_password, status)
|
insert into "user" (username, password_digest, status)
|
||||||
values ($username, $hashed_password, 'unverified')
|
values ($username, $password_digest, 'unverified')
|
||||||
returning id as user_id
|
returning id as user_id
|
||||||
), new_team as (
|
), new_team as (
|
||||||
insert into "team" (display_name)
|
insert into "team" (display_name)
|
||||||
|
@ -41,13 +41,14 @@ export const handler: Handlers<UserID | RegistrationError | null> = {
|
||||||
'owner'
|
'owner'
|
||||||
) returning user_id
|
) returning user_id
|
||||||
`,
|
`,
|
||||||
{ username, hashed_password, team_name: `${username}'s First Team` },
|
{ username, password_digest, team_name: `${username}'s First Team` },
|
||||||
);
|
);
|
||||||
console.debug(result);
|
console.debug(result);
|
||||||
if (!result) throw "insert failed";
|
if (!result) throw "insert failed";
|
||||||
const { rows: [{ user_id: id }] } = result;
|
const { rows: [{ user_id: id }] } = result;
|
||||||
return await context.render(id);
|
return await context.render(id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log("Error fields:", { ...err });
|
||||||
if (
|
if (
|
||||||
err instanceof PostgresError && err.fields.code == "23505" &&
|
err instanceof PostgresError && err.fields.code == "23505" &&
|
||||||
err.fields.constraint == "user_username_key"
|
err.fields.constraint == "user_username_key"
|
||||||
|
@ -55,6 +56,14 @@ export const handler: Handlers<UserID | RegistrationError | null> = {
|
||||||
return await context.render({
|
return await context.render({
|
||||||
message: `A user with username '${username}' already exists`,
|
message: `A user with username '${username}' already exists`,
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
err instanceof PostgresError && err.fields.code == "23514" &&
|
||||||
|
err.fields.constraint == "valid_username"
|
||||||
|
) {
|
||||||
|
return await context.render({
|
||||||
|
message:
|
||||||
|
`Username must ONLY be comprised of letters, number, dashes, and underscores`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue