Working on auth...

This commit is contained in:
Daniel Flanagan 2022-10-07 17:09:13 -05:00
parent 7a5f83c633
commit f0755615ae
Signed by untrusted user: lytedev-divvy
GPG key ID: 6D69CEEE4ABBCD82
5 changed files with 93 additions and 20 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/config.json

View file

@ -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
View 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,
},
);
}
}

View file

@ -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);

View file

@ -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;
} }