mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-13 13:50:40 +01:00
Merge branch 'main' of https://github.com/fosrl/pangolin
This commit is contained in:
commit
33990da68c
16 changed files with 215 additions and 24 deletions
18
bruno/Auth/2fa-disable.bru
Normal file
18
bruno/Auth/2fa-disable.bru
Normal 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
17
bruno/Auth/2fa-enable.bru
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
17
bruno/Auth/2fa-request.bru
Normal file
17
bruno/Auth/2fa-request.bru
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
18
bruno/Auth/change-password.bru
Normal file
18
bruno/Auth/change-password.bru
Normal 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": ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
11
bruno/Auth/logout.bru
Normal 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
|
||||||
|
}
|
17
bruno/Auth/reset-password-request.bru
Normal file
17
bruno/Auth/reset-password-request.bru
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
19
bruno/Auth/reset-password.bru
Normal file
19
bruno/Auth/reset-password.bru
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
11
bruno/Auth/verify-email-request.bru
Normal file
11
bruno/Auth/verify-email-request.bru
Normal 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
|
||||||
|
}
|
|
@ -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!"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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, {});
|
||||||
|
|
Loading…
Reference in a new issue