This commit is contained in:
Owen Schwartz 2024-10-10 22:00:32 -04:00
commit 33990da68c
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
16 changed files with 215 additions and 24 deletions

View file

@ -0,0 +1,18 @@
meta {
name: 2fa-disable
type: http
seq: 6
}
post {
url: http://localhost:3000/api/v1/auth/2fa/disable
body: json
auth: none
}
body:json {
{
"password": "aaaaa-1A",
"code": "377289"
}
}

17
bruno/Auth/2fa-enable.bru Normal file
View file

@ -0,0 +1,17 @@
meta {
name: 2fa-enable
type: http
seq: 4
}
post {
url: http://localhost:3000/api/v1/auth/2fa/enable
body: json
auth: none
}
body:json {
{
"code": "374138"
}
}

View file

@ -0,0 +1,17 @@
meta {
name: 2fa-request
type: http
seq: 5
}
post {
url: http://localhost:3000/api/v1/auth/2fa/request
body: json
auth: none
}
body:json {
{
"password": "aaaaa-1A"
}
}

View file

@ -0,0 +1,18 @@
meta {
name: change-password
type: http
seq: 9
}
post {
url: http://localhost:3000/api/v1/auth/change-password
body: json
auth: none
}
body:json {
{
"oldPassword": "",
"newPassword": ""
}
}

View file

@ -4,8 +4,15 @@ meta {
seq: 1 seq: 1
} }
get { post {
url: url: http://localhost:3000/api/v1/auth/login
body: none body: json
auth: none auth: none
} }
body:json {
{
"email": "milo@fossorial.io",
"password": "Password123!"
}
}

11
bruno/Auth/logout.bru Normal file
View file

@ -0,0 +1,11 @@
meta {
name: logout
type: http
seq: 3
}
post {
url: http://localhost:3000/api/v1/auth/logout
body: none
auth: none
}

View file

@ -0,0 +1,17 @@
meta {
name: reset-password-request
type: http
seq: 10
}
post {
url: http://localhost:3000/api/v1/auth/reset-password/request
body: json
auth: none
}
body:json {
{
"email": "milo@fossorial.io"
}
}

View file

@ -0,0 +1,19 @@
meta {
name: reset-password
type: http
seq: 11
}
post {
url: http://localhost:3000/api/v1/auth/reset-password
body: json
auth: none
}
body:json {
{
"token": "3uhsbom72dwdhboctwrtntyd6jrlg4jtf5oaxy4k",
"newPassword": "aaaaa-1A",
"code": "6irqCGR3"
}
}

View file

@ -4,8 +4,15 @@ meta {
seq: 2 seq: 2
} }
get { put {
url: url: http://localhost:3000/api/v1/auth/signup
body: none body: json
auth: none auth: none
} }
body:json {
{
"email": "milo@fossorial.io",
"password": "Password123!"
}
}

View file

@ -0,0 +1,11 @@
meta {
name: verify-email-request
type: http
seq: 8
}
post {
url: http://localhost:3000/api/v1/auth/verify-email/request
body: none
auth: none
}

View file

@ -1,18 +1,17 @@
meta { meta {
name: verify-email name: verify-email
type: http type: http
seq: 3 seq: 7
} }
put { post {
url: http://localhost:3000/api/v1/auth/signup url: http://localhost:3000/api/v1/auth/verify-email
body: json body: json
auth: none auth: none
} }
body:json { body:json {
{ {
"email": "owen@fossorial.io", "code": "50317187"
"password": "Password123!"
} }
} }

View file

@ -1,9 +1,13 @@
{ {
"version": "1", "version": "1",
"name": "Pangolin", "name": "Pangolin",
"type": "collection", "type": "collection",
"ignore": [ "ignore": [
"node_modules", "node_modules",
".git" ".git"
] ],
"presets": {
"requestType": "http",
"requestUrl": "http://localhost:3000/api/v1"
}
} }

View file

@ -39,6 +39,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"lucia": "3.2.0", "lucia": "3.2.0",
"lucide-react": "0.447.0", "lucide-react": "0.447.0",
"moment": "2.30.1",
"next": "14.2.13", "next": "14.2.13",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.9.15", "nodemailer": "6.9.15",
@ -76,4 +77,4 @@
"tsx": "4.19.1", "tsx": "4.19.1",
"typescript": "^5" "typescript": "^5"
} }
} }

View file

@ -5,7 +5,6 @@ import { Lucia, TimeSpan } from "lucia";
import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"; import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle";
import db from "@server/db"; import db from "@server/db";
import { sessions, users } from "@server/db/schema"; import { sessions, users } from "@server/db/schema";
import environment from "@server/environment";
const adapter = new DrizzleSQLiteAdapter(db, sessions, users); const adapter = new DrizzleSQLiteAdapter(db, sessions, users);
@ -16,6 +15,7 @@ export const lucia = new Lucia(adapter, {
twoFactorEnabled: attributes.twoFactorEnabled, twoFactorEnabled: attributes.twoFactorEnabled,
twoFactorSecret: attributes.twoFactorSecret, twoFactorSecret: attributes.twoFactorSecret,
emailVerified: attributes.emailVerified, emailVerified: attributes.emailVerified,
dateCreated: attributes.dateCreated,
}; };
}, },
// getSessionAttributes: (attributes) => { // getSessionAttributes: (attributes) => {
@ -30,7 +30,7 @@ export const lucia = new Lucia(adapter, {
// secure: environment.ENVIRONMENT === "prod", // secure: environment.ENVIRONMENT === "prod",
// sameSite: "strict", // sameSite: "strict",
secure: false, secure: false,
domain: ".testing123.io" domain: ".testing123.io",
}, },
}, },
sessionExpiresIn: new TimeSpan(2, "w"), sessionExpiresIn: new TimeSpan(2, "w"),
@ -52,6 +52,7 @@ interface DatabaseUserAttributes {
twoFactorEnabled: boolean; twoFactorEnabled: boolean;
twoFactorSecret?: string; twoFactorSecret?: string;
emailVerified: boolean; emailVerified: boolean;
dateCreated: string;
} }
interface DatabaseSessionAttributes { interface DatabaseSessionAttributes {

View file

@ -81,6 +81,7 @@ export const users = sqliteTable("user", {
emailVerified: integer("emailVerified", { mode: "boolean" }) emailVerified: integer("emailVerified", { mode: "boolean" })
.notNull() .notNull()
.default(false), .default(false),
dateCreated: text("dateCreated").notNull(),
}); });
export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", { export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
@ -107,7 +108,9 @@ export const userOrgs = sqliteTable("userOrgs", {
orgId: integer("orgId") orgId: integer("orgId")
.notNull() .notNull()
.references(() => orgs.orgId), .references(() => orgs.orgId),
roleId: integer("roleId").notNull().references(() => roles.roleId), roleId: integer("roleId")
.notNull()
.references(() => roles.roleId),
}); });
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
@ -205,7 +208,9 @@ export const userResources = sqliteTable("userResources", {
export const limitsTable = sqliteTable("limits", { export const limitsTable = sqliteTable("limits", {
limitId: integer("limitId").primaryKey({ autoIncrement: true }), limitId: integer("limitId").primaryKey({ autoIncrement: true }),
orgId: integer("orgId").references(() => orgs.orgId, { onDelete: "cascade" }), orgId: integer("orgId").references(() => orgs.orgId, {
onDelete: "cascade",
}),
name: text("name").notNull(), name: text("name").notNull(),
value: integer("value").notNull(), value: integer("value").notNull(),
description: text("description"), description: text("description"),
@ -233,4 +238,4 @@ export type RoleSite = InferSelectModel<typeof roleSites>;
export type UserSite = InferSelectModel<typeof userSites>; export type UserSite = InferSelectModel<typeof userSites>;
export type RoleResource = InferSelectModel<typeof roleResources>; export type RoleResource = InferSelectModel<typeof roleResources>;
export type UserResource = InferSelectModel<typeof userResources>; export type UserResource = InferSelectModel<typeof userResources>;
export type Limit = InferSelectModel<typeof limitsTable>; export type Limit = InferSelectModel<typeof limitsTable>;

View file

@ -12,6 +12,8 @@ import response from "@server/utils/response";
import { SqliteError } from "better-sqlite3"; import { SqliteError } from "better-sqlite3";
import { sendEmailVerificationCode } from "./sendEmailVerificationCode"; import { sendEmailVerificationCode } from "./sendEmailVerificationCode";
import { passwordSchema } from "@server/auth/passwordSchema"; import { passwordSchema } from "@server/auth/passwordSchema";
import { eq } from "drizzle-orm";
import moment from "moment";
export const signupBodySchema = z.object({ export const signupBodySchema = z.object({
email: z.string().email(), email: z.string().email(),
@ -51,10 +53,47 @@ export async function signup(
const userId = generateId(15); const userId = generateId(15);
try { try {
const existing = await db
.select()
.from(users)
.where(eq(users.email, email));
if (existing && existing.length > 0) {
const user = existing[0];
// If the user is already verified, we don't want to create a new user
if (user.emailVerified) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"A user with that email address already exists",
),
);
}
const dateCreated = moment(user.dateCreated);
const now = moment();
const diff = now.diff(dateCreated, "hours");
if (diff < 2) {
// If the user was created less than 2 hours ago, we don't want to create a new user
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"A verification email was already sent to this email address. Please check your email for the verification code.",
),
);
} else {
// If the user was created more than 2 hours ago, we want to delete the old user and create a new one
await db.delete(users).where(eq(users.id, user.id));
}
}
await db.insert(users).values({ await db.insert(users).values({
id: userId, id: userId,
email: email, email: email,
passwordHash, passwordHash,
dateCreated: moment().toISOString(),
}); });
const session = await lucia.createSession(userId, {}); const session = await lucia.createSession(userId, {});