mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding mfa page instead of 2FA, starting controller based password verification and totp generation.
This commit is contained in:
parent
ead20829e4
commit
df582f1534
44 changed files with 443 additions and 332 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { error, json } from '@sveltejs/kit';
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function GET({ url, locals, params }) {
|
export async function GET({ url, locals, params }) {
|
||||||
const searchParams = Object.fromEntries(url.searchParams);
|
const searchParams = Object.fromEntries(url.searchParams);
|
||||||
return json({});
|
return json({});
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { error, json } from '@sveltejs/kit';
|
import { error, json } from '@sveltejs/kit';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import db from '../../../../../db';
|
import {db} from '$lib/server/api/infrastructure/database';
|
||||||
import { collection_items, usersTable } from '$db/schema';
|
import { collection_items, usersTable } from '$db/schema';
|
||||||
|
|
||||||
// Search a user's collection
|
// Search a user's collection
|
||||||
export async function GET({ url, locals, params }) {
|
export async function GET({ url, locals, params }) {
|
||||||
const searchParams = Object.fromEntries(url.searchParams);
|
const searchParams = Object.fromEntries(url.searchParams);
|
||||||
const q = searchParams?.q || '';
|
const q = searchParams?.q || '';
|
||||||
const limit = parseInt(searchParams?.limit) || 10;
|
const limit = Number.parseInt(searchParams?.limit) || 10;
|
||||||
const skip = parseInt(searchParams?.skip) || 0;
|
const skip = Number.parseInt(searchParams?.skip) || 0;
|
||||||
const order = searchParams?.order || 'asc';
|
const order = searchParams?.order || 'asc';
|
||||||
const sort = searchParams?.sort || 'name';
|
const sort = searchParams?.sort || 'name';
|
||||||
const collection_id = params.id;
|
const collection_id = params.id;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import db from '../../../../db';
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { usersTable } from '$db/schema';
|
import { usersTable } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
|
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
|
||||||
import { PUBLIC_SITE_URL } from '$env/static/public';
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { password_reset_tokens, usersTable } from '$db/schema';
|
import { password_reset_tokens } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { isWithinExpirationDate } from 'oslo';
|
import { isWithinExpirationDate } from 'oslo';
|
||||||
import { lucia } from '$lib/server/auth.js';
|
// import { lucia } from '$lib/server/lucia';
|
||||||
import { Argon2id } from 'oslo/password';
|
import {db} from '$lib/server/api/infrastructure/database';
|
||||||
import db from '$db';
|
|
||||||
|
|
||||||
export async function POST({ request, params }) {
|
export async function POST({ request, params }) {
|
||||||
const { password } = await request.json();
|
const { password } = await request.json();
|
||||||
|
|
@ -32,12 +31,12 @@ export async function POST({ request, params }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await lucia.invalidateUserSessions(token.user_id);
|
// await lucia.invalidateUserSessions(token.user_id);
|
||||||
const hashPassword = await new Argon2id().hash(password);
|
// const hashPassword = await new Argon2id().hash(password);
|
||||||
// await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id));
|
// // await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id));
|
||||||
|
//
|
||||||
const session = await lucia.createSession(token.user_id, {});
|
// const session = await lucia.createSession(token.user_id, {});
|
||||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
// const sessionCookie = lucia.createSessionCookie(session.id);
|
||||||
|
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { error, json } from '@sveltejs/kit';
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function GET({ url, locals, params }) {
|
export async function GET({ url, locals, params }) {
|
||||||
const searchParams = Object.fromEntries(url.searchParams);
|
const searchParams = Object.fromEntries(url.searchParams);
|
||||||
return json({});
|
return json({});
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
"svelte": "5.0.0-next.175",
|
"svelte": "5.0.0-next.175",
|
||||||
"svelte-check": "^3.8.6",
|
"svelte-check": "^3.8.6",
|
||||||
"svelte-headless-table": "^0.18.2",
|
"svelte-headless-table": "^0.18.2",
|
||||||
"svelte-meta-tags": "^3.1.3",
|
"svelte-meta-tags": "^3.1.4",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
"svelte-sequential-preprocessor": "^2.0.1",
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
"sveltekit-flash-message": "^2.4.4",
|
"sveltekit-flash-message": "^2.4.4",
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.7.0",
|
||||||
"tsx": "^4.18.0",
|
"tsx": "^4.19.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.4.2",
|
"vite": "^5.4.2",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
"feather-icons": "^4.29.2",
|
"feather-icons": "^4.29.2",
|
||||||
"formsnap": "^1.0.1",
|
"formsnap": "^1.0.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"hono": "^4.5.8",
|
"hono": "^4.5.9",
|
||||||
"hono-rate-limiter": "^0.4.0",
|
"hono-rate-limiter": "^0.4.0",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"iconify-icon": "^2.1.0",
|
"iconify-icon": "^2.1.0",
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ importers:
|
||||||
version: 5.0.14
|
version: 5.0.14
|
||||||
'@hono/swagger-ui':
|
'@hono/swagger-ui':
|
||||||
specifier: ^0.4.1
|
specifier: ^0.4.1
|
||||||
version: 0.4.1(hono@4.5.8)
|
version: 0.4.1(hono@4.5.9)
|
||||||
'@hono/zod-openapi':
|
'@hono/zod-openapi':
|
||||||
specifier: ^0.15.3
|
specifier: ^0.15.3
|
||||||
version: 0.15.3(hono@4.5.8)(zod@3.23.8)
|
version: 0.15.3(hono@4.5.9)(zod@3.23.8)
|
||||||
'@hono/zod-validator':
|
'@hono/zod-validator':
|
||||||
specifier: ^0.2.2
|
specifier: ^0.2.2
|
||||||
version: 0.2.2(hono@4.5.8)(zod@3.23.8)
|
version: 0.2.2(hono@4.5.9)(zod@3.23.8)
|
||||||
'@iconify-icons/line-md':
|
'@iconify-icons/line-md':
|
||||||
specifier: ^1.2.30
|
specifier: ^1.2.30
|
||||||
version: 1.2.30
|
version: 1.2.30
|
||||||
|
|
@ -99,11 +99,11 @@ importers:
|
||||||
specifier: ^4.7.8
|
specifier: ^4.7.8
|
||||||
version: 4.7.8
|
version: 4.7.8
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.5.8
|
specifier: ^4.5.9
|
||||||
version: 4.5.8
|
version: 4.5.9
|
||||||
hono-rate-limiter:
|
hono-rate-limiter:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0(hono@4.5.8)
|
version: 0.4.0(hono@4.5.9)
|
||||||
html-entities:
|
html-entities:
|
||||||
specifier: ^2.5.2
|
specifier: ^2.5.2
|
||||||
version: 2.5.2
|
version: 2.5.2
|
||||||
|
|
@ -248,7 +248,7 @@ importers:
|
||||||
version: 16.1.0(postcss@8.4.41)
|
version: 16.1.0(postcss@8.4.41)
|
||||||
postcss-load-config:
|
postcss-load-config:
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0)
|
version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)
|
||||||
postcss-preset-env:
|
postcss-preset-env:
|
||||||
specifier: ^9.6.0
|
specifier: ^9.6.0
|
||||||
version: 9.6.0(postcss@8.4.41)
|
version: 9.6.0(postcss@8.4.41)
|
||||||
|
|
@ -272,16 +272,16 @@ importers:
|
||||||
version: 5.0.0-next.175
|
version: 5.0.0-next.175
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^3.8.6
|
specifier: ^3.8.6
|
||||||
version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)
|
version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)
|
||||||
svelte-headless-table:
|
svelte-headless-table:
|
||||||
specifier: ^0.18.2
|
specifier: ^0.18.2
|
||||||
version: 0.18.2(svelte@5.0.0-next.175)
|
version: 0.18.2(svelte@5.0.0-next.175)
|
||||||
svelte-meta-tags:
|
svelte-meta-tags:
|
||||||
specifier: ^3.1.3
|
specifier: ^3.1.4
|
||||||
version: 3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4)
|
version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4)
|
||||||
svelte-preprocess:
|
svelte-preprocess:
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4)
|
version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4)
|
||||||
svelte-sequential-preprocessor:
|
svelte-sequential-preprocessor:
|
||||||
specifier: ^2.0.1
|
specifier: ^2.0.1
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
|
@ -304,8 +304,8 @@ importers:
|
||||||
specifier: ^2.7.0
|
specifier: ^2.7.0
|
||||||
version: 2.7.0
|
version: 2.7.0
|
||||||
tsx:
|
tsx:
|
||||||
specifier: ^4.18.0
|
specifier: ^4.19.0
|
||||||
version: 4.18.0
|
version: 4.19.0
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.5.4
|
specifier: ^5.5.4
|
||||||
version: 5.5.4
|
version: 5.5.4
|
||||||
|
|
@ -3178,8 +3178,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
hono: ^4.1.1
|
hono: ^4.1.1
|
||||||
|
|
||||||
hono@4.5.8:
|
hono@4.5.9:
|
||||||
resolution: {integrity: sha512-pqpSlcdqGkpTTRpLYU1PnCz52gVr0zVR9H5GzMyJWuKQLLEBQxh96q45QizJ2PPX8NATtz2mu31/PKW/Jt+90Q==}
|
resolution: {integrity: sha512-zz8ktqMDRrZETjxBrv8C5PQRFbrTRCLNVAjD1SNQyOzv4VjmX68Uxw83xQ6oxdAB60HiWnGEatiKA8V3SZLDkQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
html-entities@2.5.2:
|
html-entities@2.5.2:
|
||||||
|
|
@ -4493,8 +4493,8 @@ packages:
|
||||||
svelte-lazy-loader@1.0.0:
|
svelte-lazy-loader@1.0.0:
|
||||||
resolution: {integrity: sha512-AZD6R60vksyojn21FgXLglmBiBB9K5Dkdu0hdGrLbCaRCYT68IsWkZfRUqKhMx1IfzqWcZQ8X9y/f+Ih0oNQkQ==}
|
resolution: {integrity: sha512-AZD6R60vksyojn21FgXLglmBiBB9K5Dkdu0hdGrLbCaRCYT68IsWkZfRUqKhMx1IfzqWcZQ8X9y/f+Ih0oNQkQ==}
|
||||||
|
|
||||||
svelte-meta-tags@3.1.3:
|
svelte-meta-tags@3.1.4:
|
||||||
resolution: {integrity: sha512-iIdJgxKdMUqFGR4m88jBE9KTSO2jdKE5CRjyRtAjdevW51jL4TtDZwL7GOtr5Fd2dw/+jyQIPD7APATP191qIA==}
|
resolution: {integrity: sha512-TUIfhut0iVeTm7f5v/ZuU/tZ9XsNig9bNN8yK0t2x2WL9qw6AxAVRe9i5XddYJE0SuVwkoDCzjoSg5hXv7oWbQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^3.55.0 || ^4.0.0
|
svelte: ^3.55.0 || ^4.0.0
|
||||||
|
|
||||||
|
|
@ -4737,8 +4737,8 @@ packages:
|
||||||
tslib@2.7.0:
|
tslib@2.7.0:
|
||||||
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
||||||
|
|
||||||
tsx@4.18.0:
|
tsx@4.19.0:
|
||||||
resolution: {integrity: sha512-a1jaKBSVQkd6yEc1/NI7G6yHFfefIcuf3QJST7ZEyn4oQnxLYrZR5uZAM8UrwUa3Ge8suiZHcNS1gNrEvmobqg==}
|
resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
|
@ -5695,20 +5695,20 @@ snapshots:
|
||||||
'@hapi/hoek': 9.3.0
|
'@hapi/hoek': 9.3.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@hono/swagger-ui@0.4.1(hono@4.5.8)':
|
'@hono/swagger-ui@0.4.1(hono@4.5.9)':
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.5.8
|
hono: 4.5.9
|
||||||
|
|
||||||
'@hono/zod-openapi@0.15.3(hono@4.5.8)(zod@3.23.8)':
|
'@hono/zod-openapi@0.15.3(hono@4.5.9)(zod@3.23.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8)
|
'@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8)
|
||||||
'@hono/zod-validator': 0.2.2(hono@4.5.8)(zod@3.23.8)
|
'@hono/zod-validator': 0.2.2(hono@4.5.9)(zod@3.23.8)
|
||||||
hono: 4.5.8
|
hono: 4.5.9
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
|
|
||||||
'@hono/zod-validator@0.2.2(hono@4.5.8)(zod@3.23.8)':
|
'@hono/zod-validator@0.2.2(hono@4.5.9)(zod@3.23.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.5.8
|
hono: 4.5.9
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
|
|
||||||
'@humanwhocodes/config-array@0.11.14':
|
'@humanwhocodes/config-array@0.11.14':
|
||||||
|
|
@ -7562,11 +7562,11 @@ snapshots:
|
||||||
|
|
||||||
hex-rgb@4.3.0: {}
|
hex-rgb@4.3.0: {}
|
||||||
|
|
||||||
hono-rate-limiter@0.4.0(hono@4.5.8):
|
hono-rate-limiter@0.4.0(hono@4.5.9):
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.5.8
|
hono: 4.5.9
|
||||||
|
|
||||||
hono@4.5.8: {}
|
hono@4.5.9: {}
|
||||||
|
|
||||||
html-entities@2.5.2: {}
|
html-entities@2.5.2: {}
|
||||||
|
|
||||||
|
|
@ -8301,14 +8301,14 @@ snapshots:
|
||||||
postcss: 8.4.41
|
postcss: 8.4.41
|
||||||
ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4)
|
ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4)
|
||||||
|
|
||||||
postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0):
|
postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
lilconfig: 3.1.1
|
lilconfig: 3.1.1
|
||||||
yaml: 2.4.2
|
yaml: 2.4.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
jiti: 1.21.6
|
jiti: 1.21.6
|
||||||
postcss: 8.4.41
|
postcss: 8.4.41
|
||||||
tsx: 4.18.0
|
tsx: 4.19.0
|
||||||
|
|
||||||
postcss-logical@7.0.1(postcss@8.4.41):
|
postcss-logical@7.0.1(postcss@8.4.41):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -8878,14 +8878,14 @@ snapshots:
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0: {}
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175):
|
svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
chokidar: 3.6.0
|
chokidar: 3.6.0
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
sade: 1.8.1
|
sade: 1.8.1
|
||||||
svelte: 5.0.0-next.175
|
svelte: 5.0.0-next.175
|
||||||
svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4)
|
svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4)
|
||||||
typescript: 5.5.4
|
typescript: 5.5.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
|
|
@ -8930,7 +8930,7 @@ snapshots:
|
||||||
|
|
||||||
svelte-lazy-loader@1.0.0: {}
|
svelte-lazy-loader@1.0.0: {}
|
||||||
|
|
||||||
svelte-meta-tags@3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4):
|
svelte-meta-tags@3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
schema-dts: 1.1.2(typescript@5.5.4)
|
schema-dts: 1.1.2(typescript@5.5.4)
|
||||||
svelte: 5.0.0-next.175
|
svelte: 5.0.0-next.175
|
||||||
|
|
@ -8941,7 +8941,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.0.0-next.175
|
svelte: 5.0.0-next.175
|
||||||
|
|
||||||
svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4):
|
svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/pug': 2.0.10
|
'@types/pug': 2.0.10
|
||||||
detect-indent: 6.1.0
|
detect-indent: 6.1.0
|
||||||
|
|
@ -8951,16 +8951,16 @@ snapshots:
|
||||||
svelte: 5.0.0-next.175
|
svelte: 5.0.0-next.175
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
postcss: 8.4.41
|
postcss: 8.4.41
|
||||||
postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0)
|
postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)
|
||||||
sass: 1.77.8
|
sass: 1.77.8
|
||||||
typescript: 5.5.4
|
typescript: 5.5.4
|
||||||
|
|
||||||
svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4):
|
svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.0.0-next.175
|
svelte: 5.0.0-next.175
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
postcss: 8.4.41
|
postcss: 8.4.41
|
||||||
postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0)
|
postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)
|
||||||
sass: 1.77.8
|
sass: 1.77.8
|
||||||
typescript: 5.5.4
|
typescript: 5.5.4
|
||||||
|
|
||||||
|
|
@ -9174,7 +9174,7 @@ snapshots:
|
||||||
|
|
||||||
tslib@2.7.0: {}
|
tslib@2.7.0: {}
|
||||||
|
|
||||||
tsx@4.18.0:
|
tsx@4.19.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.23.0
|
esbuild: 0.23.0
|
||||||
get-tsconfig: 4.7.5
|
get-tsconfig: 4.7.5
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/login"> <span class="flex-auto">Login</span></a>
|
<a href="/login"> <span class="flex-auto">Login</span></a>
|
||||||
<a href="/sign-up"> <span class="flex-auto">Sign Up</span></a>
|
<a href="/signup"> <span class="flex-auto">Sign Up</span></a>
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
// $: termsValue = $form.terms as Writable<boolean>;
|
// $: termsValue = $form.terms as Writable<boolean>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form method="POST" action="/sign-up" use:enhance>
|
<form method="POST" action="/signup" use:enhance>
|
||||||
<h1>Signup user</h1>
|
<h1>Signup user</h1>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="sr-only">First Name</span>
|
<span class="sr-only">First Name</span>
|
||||||
|
|
|
||||||
7
src/lib/dtos/verify-password.dto.ts
Normal file
7
src/lib/dtos/verify-password.dto.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const verifyPasswordDto = z.object({
|
||||||
|
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type VerifyPasswordDto = z.infer<typeof verifyPasswordDto>
|
||||||
|
|
@ -11,6 +11,7 @@ import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||||
import { updateProfileDto } from '$lib/dtos/update-profile.dto'
|
import { updateProfileDto } from '$lib/dtos/update-profile.dto'
|
||||||
import { updateEmailDto } from '$lib/dtos/update-email.dto'
|
import { updateEmailDto } from '$lib/dtos/update-email.dto'
|
||||||
import { StatusCodes } from '$lib/constants/status-codes'
|
import { StatusCodes } from '$lib/constants/status-codes'
|
||||||
|
import { verifyPasswordDto } from '$lib/dtos/verify-password.dto'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IamController implements Controller {
|
export class IamController implements Controller {
|
||||||
|
|
@ -36,6 +37,15 @@ export class IamController implements Controller {
|
||||||
}
|
}
|
||||||
return c.json({ user: updatedUser }, StatusCodes.OK)
|
return c.json({ user: updatedUser }, StatusCodes.OK)
|
||||||
})
|
})
|
||||||
|
.post('/verify/password', requireAuth, zValidator('json', verifyPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||||
|
const user = c.var.user
|
||||||
|
const { password } = c.req.valid('json')
|
||||||
|
const passwordVerified = await this.iamService.verifyPassword(user.id, { password })
|
||||||
|
if (!passwordVerified) {
|
||||||
|
return c.json('Incorrect password', StatusCodes.BAD_REQUEST)
|
||||||
|
}
|
||||||
|
return c.json({ }, StatusCodes.OK)
|
||||||
|
})
|
||||||
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||||
const user = c.var.user
|
const user = c.var.user
|
||||||
const { email } = c.req.valid('json')
|
const { email } = c.req.valid('json')
|
||||||
|
|
|
||||||
39
src/lib/server/api/controllers/mfa.controller.ts
Normal file
39
src/lib/server/api/controllers/mfa.controller.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware";
|
||||||
|
import type { HonoTypes } from '../types';
|
||||||
|
import type { Controller } from '$lib/server/api/interfaces/controller.interface';
|
||||||
|
import { TotpService } from '$lib/server/api/services/totp.service';
|
||||||
|
import {StatusCodes} from "$lib/constants/status-codes";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MfaController implements Controller {
|
||||||
|
controller = new Hono<HonoTypes>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(TotpService) private readonly totpService: TotpService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
routes() {
|
||||||
|
return this.controller
|
||||||
|
.get('/totp', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user;
|
||||||
|
const totpCredential = await this.totpService.findOneByUserId(user.id);
|
||||||
|
return c.json({ totpCredential });
|
||||||
|
})
|
||||||
|
.post('/totp', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user;
|
||||||
|
const totpCredential = await this.totpService.create(user.id);
|
||||||
|
return c.json({ totpCredential })
|
||||||
|
})
|
||||||
|
.delete('/totp', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user;
|
||||||
|
await this.totpService.deleteOneByUserId(user.id);
|
||||||
|
return c.status(StatusCodes.NO_CONTENT);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { config } from './common/config';
|
||||||
import { container } from 'tsyringe';
|
import { container } from 'tsyringe';
|
||||||
import { IamController } from './controllers/iam.controller';
|
import { IamController } from './controllers/iam.controller';
|
||||||
import { LoginController } from './controllers/login.controller';
|
import { LoginController } from './controllers/login.controller';
|
||||||
|
import { MfaController} from "$lib/server/api/controllers/mfa.controller";
|
||||||
import {UserController} from "$lib/server/api/controllers/user.controller";
|
import {UserController} from "$lib/server/api/controllers/user.controller";
|
||||||
import {SignupController} from "$lib/server/api/controllers/signup.controller";
|
import {SignupController} from "$lib/server/api/controllers/signup.controller";
|
||||||
import {WishlistController} from "$lib/server/api/controllers/wishlist.controller";
|
import {WishlistController} from "$lib/server/api/controllers/wishlist.controller";
|
||||||
|
|
@ -44,6 +45,7 @@ const routes = app
|
||||||
.route('/signup', container.resolve(SignupController).routes())
|
.route('/signup', container.resolve(SignupController).routes())
|
||||||
.route('/wishlists', container.resolve(WishlistController).routes())
|
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||||
.route('/collections', container.resolve(CollectionController).routes())
|
.route('/collections', container.resolve(CollectionController).routes())
|
||||||
|
.route('/mfa', container.resolve(MfaController).routes())
|
||||||
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ for (const table of [
|
||||||
schema.publishers,
|
schema.publishers,
|
||||||
schema.publishersToExternalIds,
|
schema.publishersToExternalIds,
|
||||||
schema.publishers_to_games,
|
schema.publishers_to_games,
|
||||||
schema.recovery_codes,
|
schema.recoveryCodesTable,
|
||||||
schema.roles,
|
schema.roles,
|
||||||
schema.sessionsTable,
|
schema.sessionsTable,
|
||||||
schema.twoFactorTable,
|
schema.twoFactorTable,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export * from './passwordResetTokens';
|
||||||
export * from './publishers';
|
export * from './publishers';
|
||||||
export * from './publishersToExternalIds';
|
export * from './publishersToExternalIds';
|
||||||
export * from './publishersToGames';
|
export * from './publishersToGames';
|
||||||
export * from './recoveryCodes';
|
export * from './recovery-codes.table';
|
||||||
export * from './roles';
|
export * from './roles';
|
||||||
export * from './sessions.table';
|
export * from './sessions.table';
|
||||||
export * from './two-factor.table';
|
export * from './two-factor.table';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import { usersTable } from './users.table';
|
import { usersTable } from './users.table';
|
||||||
import { timestamps } from '../utils';
|
import { timestamps } from '../utils';
|
||||||
|
|
||||||
export const recovery_codes = pgTable('recovery_codes', {
|
export const recoveryCodesTable = pgTable('recovery_codes', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
userId: uuid('user_id')
|
userId: uuid('user_id')
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|
@ -13,4 +13,4 @@ export const recovery_codes = pgTable('recovery_codes', {
|
||||||
...timestamps,
|
...timestamps,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;
|
export type RecoveryCodesTable = InferSelectModel<typeof recoveryCodesTable>;
|
||||||
|
|
@ -17,6 +17,15 @@ export class CredentialsRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneByUserIdAndType(userId: string, type: CredentialsType) {
|
||||||
|
return this.db.query.credentialsTable.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(credentialsTable.user_id, userId),
|
||||||
|
eq(credentialsTable.type, type)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async findPasswordCredentialsByUserId(userId: string) {
|
async findPasswordCredentialsByUserId(userId: string) {
|
||||||
return this.db.query.credentialsTable.findFirst({
|
return this.db.query.credentialsTable.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
|
|
@ -59,4 +68,16 @@ export class CredentialsRepository {
|
||||||
.returning()
|
.returning()
|
||||||
.then(takeFirstOrThrow);
|
.then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
return this.db
|
||||||
|
.delete(credentialsTable)
|
||||||
|
.where(eq(credentialsTable.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteByUserId(userId: string) {
|
||||||
|
return this.db
|
||||||
|
.delete(credentialsTable)
|
||||||
|
.where(eq(credentialsTable.user_id, userId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,4 +66,13 @@ export class IamService {
|
||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verifyPassword(userId: string, data: VerifyPasswordDto) {
|
||||||
|
const user = await this.usersService.findOneById(userId);
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { password } = data;
|
||||||
|
return this.usersService.verifyPassword(userId, { password });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
src/lib/server/api/services/totp.service.ts
Normal file
36
src/lib/server/api/services/totp.service.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import { HMAC } from 'oslo/crypto';
|
||||||
|
import { encodeHex } from 'oslo/encoding';
|
||||||
|
import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class TotpService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneByUserId(userId: string) {
|
||||||
|
return this.credentialsRepository.findTOTPCredentialsByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(userId: string) {
|
||||||
|
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.credentialsRepository.create({
|
||||||
|
user_id: userId,
|
||||||
|
secret_data: encodeHex(twoFactorSecret),
|
||||||
|
type: 'totp'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteOneByUserId(userId: string) {
|
||||||
|
return this.credentialsRepository.deleteByUserId(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,4 +68,17 @@ export class UsersService {
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.usersRepository.findOneById(id);
|
return this.usersRepository.findOneById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verifyPassword(userId: string, data: { password: string }) {
|
||||||
|
const user = await this.usersRepository.findOneById(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
|
const credential = await this.credentialsRepository.findOneByUserIdAndType(userId, CredentialsType.PASSWORD);
|
||||||
|
if (!credential) {
|
||||||
|
throw new Error('Password credentials not found');
|
||||||
|
}
|
||||||
|
const { password } = data;
|
||||||
|
return this.tokenService.verifyHashedToken(credential.secret_data, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { redirect } from 'sveltekit-flash-message/server';
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import db from '../../../../db';
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { wishlists } from '$db/schema';
|
import { wishlists } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
|
||||||
import { notSignedInMessage } from '$lib/flashMessages';
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
|
|
||||||
export async function load(event) {
|
export async function load(event) {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbWishlists = await db.query.wishlists.findMany({
|
const dbWishlists = await db.query.wishlists.findMany({
|
||||||
where: eq(wishlists.user_id, user!.id!),
|
where: eq(wishlists.user_id, authedUser.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,129 +10,129 @@ import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { usersTable, credentialsTable } from '$lib/server/api/infrastructure/database/tables';
|
import { usersTable, credentialsTable } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||||
import {updateProfileDto} from "$lib/dtos/update-profile.dto";
|
import { updateProfileDto } from "$lib/dtos/update-profile.dto";
|
||||||
import {updateEmailDto} from "$lib/dtos/update-email.dto";
|
import { updateEmailDto } from "$lib/dtos/update-email.dto";
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
|
|
||||||
const authedUser = await locals.getAuthedUser();
|
const authedUser = await locals.getAuthedUser();
|
||||||
if (!authedUser) {
|
if (!authedUser) {
|
||||||
throw redirect(302, '/login', notSignedInMessage, event);
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('authedUser', authedUser);
|
console.log('authedUser', authedUser);
|
||||||
// if (userNotAuthenticated(user, session)) {
|
// if (userNotAuthenticated(user, session)) {
|
||||||
// redirect(302, '/login', notSignedInMessage, event);
|
// redirect(302, '/login', notSignedInMessage, event);
|
||||||
// }
|
// }
|
||||||
// const dbUser = await db.query.usersTable.findFirst({
|
// const dbUser = await db.query.usersTable.findFirst({
|
||||||
// where: eq(usersTable.id, user!.id!),
|
// where: eq(usersTable.id, user!.id!),
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const profileForm = await superValidate(zod(profileSchema), {
|
const profileForm = await superValidate(zod(profileSchema), {
|
||||||
defaults: {
|
defaults: {
|
||||||
firstName: authedUser?.firstName ?? '',
|
firstName: authedUser?.firstName ?? '',
|
||||||
lastName: authedUser?.lastName ?? '',
|
lastName: authedUser?.lastName ?? '',
|
||||||
username: authedUser?.username ?? '',
|
username: authedUser?.username ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const emailForm = await superValidate(zod(changeEmailSchema), {
|
const emailForm = await superValidate(zod(changeEmailSchema), {
|
||||||
defaults: {
|
defaults: {
|
||||||
email: authedUser?.email ?? '',
|
email: authedUser?.email ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// const twoFactorDetails = await db.query.twoFactor.findFirst({
|
// const twoFactorDetails = await db.query.twoFactor.findFirst({
|
||||||
// where: eq(twoFactor.userId, authedUser!.id!),
|
// where: eq(twoFactor.userId, authedUser!.id!),
|
||||||
// });
|
// });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
profileForm,
|
profileForm,
|
||||||
emailForm,
|
emailForm,
|
||||||
hasSetupTwoFactor: false //!!twoFactorDetails?.enabled,
|
hasSetupTwoFactor: false //!!twoFactorDetails?.enabled,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeEmailIfNotEmpty = z.object({
|
const changeEmailIfNotEmpty = z.object({
|
||||||
email: z
|
email: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.max(64, { message: 'Email must be less than 64 characters' })
|
.max(64, { message: 'Email must be less than 64 characters' })
|
||||||
.email({ message: 'Please enter a valid email' }),
|
.email({ message: 'Please enter a valid email' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
profileUpdate: async (event) => {
|
profileUpdate: async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
|
|
||||||
const authedUser = await locals.getAuthedUser();
|
const authedUser = await locals.getAuthedUser();
|
||||||
|
|
||||||
if (!authedUser) {
|
if (!authedUser) {
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = await superValidate(event, zod(updateProfileDto));
|
const form = await superValidate(event, zod(updateProfileDto));
|
||||||
|
|
||||||
const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parwseApiResponse);
|
const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parseApiResponse);
|
||||||
console.log('data from profile update', error);
|
console.log('data from profile update', error);
|
||||||
if (error) {
|
if (error) {
|
||||||
return setError(form, 'username', error);
|
return setError(form, 'username', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
form,
|
form,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('profile updated successfully');
|
console.log('profile updated successfully');
|
||||||
return message(form, { type: 'success', message: 'Profile updated successfully!' });
|
return message(form, { type: 'success', message: 'Profile updated successfully!' });
|
||||||
},
|
},
|
||||||
changeEmail: async (event) => {
|
changeEmail: async (event) => {
|
||||||
const form = await superValidate(event, zod(updateEmailDto));
|
const form = await superValidate(event, zod(updateEmailDto));
|
||||||
|
|
||||||
const newEmail = form.data?.email;
|
const newEmail = form.data?.email;
|
||||||
if (
|
if (
|
||||||
!form.valid ||
|
!form.valid ||
|
||||||
!newEmail ||
|
!newEmail ||
|
||||||
(newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success)
|
(newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success)
|
||||||
) {
|
) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
form,
|
form,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.locals.user) {
|
if (!event.locals.user) {
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = event.locals.user;
|
const user = event.locals.user;
|
||||||
const existingUser = await db.query.usersTable.findFirst({
|
const existingUser = await db.query.usersTable.findFirst({
|
||||||
where: eq(usersTable.email, newEmail),
|
where: eq(usersTable.email, newEmail),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingUser && existingUser.id !== user.id) {
|
if (existingUser && existingUser.id !== user.id) {
|
||||||
return setError(form, 'email', 'That email is already taken');
|
return setError(form, 'email', 'That email is already taken');
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id));
|
await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id));
|
||||||
|
|
||||||
// if (user.email !== form.data.email) {
|
// if (user.email !== form.data.email) {
|
||||||
// Send email to confirm new email?
|
// Send email to confirm new email?
|
||||||
// auth.update
|
// auth.update
|
||||||
// await locals.prisma.key.update({
|
// await locals.prisma.key.update({
|
||||||
// where: {
|
// where: {
|
||||||
// id: 'emailpassword:' + user.email
|
// id: 'emailpassword:' + user.email
|
||||||
// },
|
// },
|
||||||
// data: {
|
// data: {
|
||||||
// id: 'emailpassword:' + form.data.email
|
// id: 'emailpassword:' + form.data.email
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
// auth.updateUserAttributes(user.user_id, {
|
// auth.updateUserAttributes(user.user_id, {
|
||||||
// receiveEmail: false
|
// receiveEmail: false
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return message(form, { type: 'success', message: 'Email updated successfully!' });
|
return message(form, { type: 'success', message: 'Email updated successfully!' });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -96,16 +96,16 @@
|
||||||
</form>
|
</form>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
{#if !hasSetupTwoFactor}
|
{#if !hasSetupTwoFactor}
|
||||||
<p>Two Factor Authentication is: <strong>Disabled</strong></p>
|
<p>Multi Factor Authentication is: <strong>Disabled</strong></p>
|
||||||
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
|
<Button variant="link" class="text-secondary-foreground" href="/profile/security/mfa">
|
||||||
<KeyRound class="mr-2 h-4 w-4" />
|
<KeyRound class="mr-2 h-4 w-4" />
|
||||||
Setup 2FA
|
Setup Multi-factor Authentication
|
||||||
</Button>
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<p>Two Factor Authentication is: <strong>Enabled</strong></p>
|
<p>Multi Factor Authentication is: <strong>Enabled</strong></p>
|
||||||
<Button variant="link" class="text-secondary-foreground" href="/profile/security/two-factor">
|
<Button variant="link" class="text-secondary-foreground" href="/profile/security/mfa">
|
||||||
<KeyRound class="mr-2 h-4 w-4" />
|
<KeyRound class="mr-2 h-4 w-4" />
|
||||||
Disable 2FA
|
Disable Multi-factor Authentication
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,30 +12,32 @@ import { redirect, setFlash } from 'sveltekit-flash-message/server';
|
||||||
import type { PageServerLoad } from '../../$types';
|
import type { PageServerLoad } from '../../$types';
|
||||||
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
|
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
|
||||||
import { notSignedInMessage } from '$lib/flashMessages';
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
import db from '$lib/server/api/infrastructure/database';
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { recoveryCodes, twoFactor, usersTable } from '$lib/server/api/infrastructure/database/tables';
|
import { recoveryCodesTable, credentialsTable, usersTable, type Credentials } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||||
import env from '../../../../../../env';
|
import env from '$src/env';
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
|
||||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
|
||||||
|
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
||||||
where: eq(usersTable.id, user!.id!),
|
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
||||||
});
|
// const addAuthNFactorForm = await superValidate(event, zod(addAuthNFactorSchema));
|
||||||
|
|
||||||
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse);
|
||||||
where: eq(twoFactor.userId, dbUser!.id!),
|
if (error || !data) {
|
||||||
});
|
return fail(500, {
|
||||||
|
addTwoFactorForm,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { totpCredential } = data
|
||||||
|
|
||||||
if (twoFactorDetails?.enabled) {
|
if (totpCredential) {
|
||||||
return {
|
return {
|
||||||
addTwoFactorForm,
|
addTwoFactorForm,
|
||||||
removeTwoFactorForm,
|
removeTwoFactorForm,
|
||||||
|
|
@ -43,35 +45,27 @@ export const load: PageServerLoad = async (event) => {
|
||||||
recoveryCodes: [],
|
recoveryCodes: [],
|
||||||
totpUri: '',
|
totpUri: '',
|
||||||
qrCode: '',
|
qrCode: '',
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(twoFactor)
|
|
||||||
.values({
|
|
||||||
secret: encodeHex(twoFactorSecret),
|
|
||||||
enabled: false,
|
|
||||||
userId: dbUser!.id!,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: twoFactor.userId,
|
|
||||||
set: {
|
|
||||||
secret: encodeHex(twoFactorSecret),
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
error(500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuer = kebabCase(env.PUBLIC_SITE_NAME);
|
const issuer = kebabCase(env.PUBLIC_SITE_NAME);
|
||||||
const accountName = dbUser!.email! || dbUser!.username!;
|
const accountName = authedUser.email || authedUser.username;
|
||||||
|
const { data: createdTotpData, error: createdTotpError } = await locals.api.mfa.totp.$post().then(locals.parseApiResponse);
|
||||||
|
|
||||||
|
if (createdTotpError || !createdTotpData) {
|
||||||
|
return fail(500, {
|
||||||
|
addTwoFactorForm,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { totpCredential: createdTotpCredentials } = createdTotpData;
|
||||||
// pass the website's name and the user identifier (e.g. email, username)
|
// pass the website's name and the user identifier (e.g. email, username)
|
||||||
const totpUri = createTOTPKeyURI(issuer, accountName, twoFactorSecret);
|
if (!createdTotpCredentials?.secret_data) {
|
||||||
|
return fail(500, {
|
||||||
|
addTwoFactorForm,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const totpUri = createTOTPKeyURI(issuer, accountName, createdTotpCredentials.secret_data);
|
||||||
|
|
||||||
addTwoFactorForm.data = {
|
addTwoFactorForm.data = {
|
||||||
current_password: '',
|
current_password: '',
|
||||||
|
|
@ -88,111 +82,85 @@ export const load: PageServerLoad = async (event) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
enableTwoFactor: async (event) => {
|
enableTotp: async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser()
|
||||||
return fail(401);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema))
|
||||||
|
|
||||||
if (!addTwoFactorForm.valid) {
|
if (!addTwoFactorForm.valid) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
addTwoFactorForm,
|
addTwoFactorForm,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.locals.user) {
|
const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse)
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (error || !data) {
|
||||||
}
|
return fail(500, {
|
||||||
|
|
||||||
if (!event.locals.session) {
|
|
||||||
return fail(401);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
|
||||||
where: eq(usersTable.id, user!.id!),
|
|
||||||
});
|
|
||||||
|
|
||||||
// if (!dbUser?.hashed_password) {
|
|
||||||
// addTwoFactorForm.data.current_password = '';
|
|
||||||
// addTwoFactorForm.data.two_factor_code = '';
|
|
||||||
// return setError(
|
|
||||||
// addTwoFactorForm,
|
|
||||||
// 'Error occurred. Please try again or contact support if you need further help.',
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
|
||||||
where: eq(twoFactor.userId, dbUser?.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!twoFactorDetails) {
|
|
||||||
addTwoFactorForm.data.current_password = '';
|
|
||||||
addTwoFactorForm.data.two_factor_code = '';
|
|
||||||
return setError(
|
|
||||||
addTwoFactorForm,
|
addTwoFactorForm,
|
||||||
'Error occurred. Please try again or contact support if you need further help.',
|
})
|
||||||
);
|
}
|
||||||
|
const { totpCredential } = data
|
||||||
|
|
||||||
|
if (!totpCredential) {
|
||||||
|
addTwoFactorForm.data.current_password = ''
|
||||||
|
addTwoFactorForm.data.two_factor_code = ''
|
||||||
|
return setError(addTwoFactorForm, 'Error occurred. Please try again or contact support if you need further help.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (twoFactorDetails.secret === '' || twoFactorDetails.secret === null) {
|
if (totpCredential.secret_data === '' || totpCredential.secret_data === null) {
|
||||||
addTwoFactorForm.data.current_password = '';
|
addTwoFactorForm.data.current_password = ''
|
||||||
addTwoFactorForm.data.two_factor_code = '';
|
addTwoFactorForm.data.two_factor_code = ''
|
||||||
return setError(
|
return setError(addTwoFactorForm, 'Error occurred. Please try again or contact support if you need further help.')
|
||||||
addTwoFactorForm,
|
|
||||||
'Error occurred. Please try again or contact support if you need further help.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPasswordVerified = await new Argon2id().verify(
|
const currentPasswordVerified = await locals.api.me.verify.password.$post({
|
||||||
// dbUser.hashed_password,
|
json: { password: addTwoFactorForm.data.current_password },
|
||||||
addTwoFactorForm.data.current_password,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentPasswordVerified) {
|
if (!currentPasswordVerified) {
|
||||||
return setError(addTwoFactorForm, 'current_password', 'Your password is incorrect');
|
return setError(addTwoFactorForm, 'current_password', 'Your password is incorrect')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addTwoFactorForm.data.two_factor_code === '') {
|
if (addTwoFactorForm.data.two_factor_code === '') {
|
||||||
return setError(addTwoFactorForm, 'two_factor_code', 'Please enter a code');
|
return setError(addTwoFactorForm, 'two_factor_code', 'Please enter a code')
|
||||||
}
|
}
|
||||||
|
|
||||||
const twoFactorCode = addTwoFactorForm.data.two_factor_code;
|
const twoFactorCode = addTwoFactorForm.data.two_factor_code
|
||||||
const validOTP = await new TOTPController().verify(
|
const validOTP = await new TOTPController().verify(twoFactorCode, decodeHex(twoFactorDetails.secret))
|
||||||
twoFactorCode,
|
|
||||||
decodeHex(twoFactorDetails.secret),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!validOTP) {
|
if (!validOTP) {
|
||||||
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code');
|
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code')
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.update(twoFactor).set({ enabled: true }).where(eq(twoFactor.userId, user!.id!));
|
await db.update(twoFactor).set({ enabled: true }).where(eq(twoFactor.userId, user!.id!))
|
||||||
|
|
||||||
redirect(302, '/profile/security/two-factor/recovery-codes');
|
redirect(302, '/profile/security/two-factor/recovery-codes')
|
||||||
},
|
},
|
||||||
disableTwoFactor: async (event) => {
|
disableTotp: async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event
|
||||||
const { user, session } = locals;
|
const { user, session } = locals
|
||||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema))
|
||||||
|
|
||||||
if (!removeTwoFactorForm.valid) {
|
if (!removeTwoFactorForm.valid) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
removeTwoFactorForm,
|
removeTwoFactorForm,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user || !session) {
|
if (!user || !session) {
|
||||||
return fail(401, {
|
return fail(401, {
|
||||||
removeTwoFactorForm,
|
removeTwoFactorForm,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
const dbUser = await db.query.usersTable.findFirst({
|
||||||
where: eq(usersTable.id, user.id),
|
where: eq(usersTable.id, user.id),
|
||||||
});
|
})
|
||||||
|
|
||||||
// if (!dbUser?.hashed_password) {
|
// if (!dbUser?.hashed_password) {
|
||||||
// removeTwoFactorForm.data.current_password = '';
|
// removeTwoFactorForm.data.current_password = '';
|
||||||
|
|
@ -205,24 +173,24 @@ export const actions: Actions = {
|
||||||
const currentPasswordVerified = await new Argon2id().verify(
|
const currentPasswordVerified = await new Argon2id().verify(
|
||||||
// dbUser.hashed_password,
|
// dbUser.hashed_password,
|
||||||
removeTwoFactorForm.data.current_password,
|
removeTwoFactorForm.data.current_password,
|
||||||
);
|
)
|
||||||
|
|
||||||
if (!currentPasswordVerified) {
|
if (!currentPasswordVerified) {
|
||||||
return setError(removeTwoFactorForm, 'current_password', 'Your password is incorrect');
|
return setError(removeTwoFactorForm, 'current_password', 'Your password is incorrect')
|
||||||
}
|
}
|
||||||
|
|
||||||
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
||||||
where: eq(twoFactor.userId, dbUser.id),
|
where: eq(twoFactor.userId, dbUser.id),
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!twoFactorDetails) {
|
if (!twoFactorDetails) {
|
||||||
return fail(500, {
|
return fail(500, {
|
||||||
removeTwoFactorForm,
|
removeTwoFactorForm,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.update(twoFactor).set({ enabled: false }).where(eq(twoFactor.userId, user.id));
|
await db.update(twoFactor).set({ enabled: false }).where(eq(twoFactor.userId, user.id))
|
||||||
await db.delete(recoveryCodes).where(eq(recoveryCodes.userId, user.id));
|
await db.delete(recoveryCodes).where(eq(recoveryCodes.userId, user.id))
|
||||||
|
|
||||||
// setFlash({ type: 'success', message: 'Two-Factor Authentication has been disabled.' }, cookies);
|
// setFlash({ type: 'success', message: 'Two-Factor Authentication has been disabled.' }, cookies);
|
||||||
redirect(
|
redirect(
|
||||||
|
|
@ -233,6 +201,6 @@ export const actions: Actions = {
|
||||||
message: 'Two-Factor Authentication has been disabled.',
|
message: 'Two-Factor Authentication has been disabled.',
|
||||||
},
|
},
|
||||||
event,
|
event,
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
{#if twoFactorEnabled}
|
{#if twoFactorEnabled}
|
||||||
<h2>Currently you have two factor authentication <span class="text-green-500">enabled</span></h2>
|
<h2>Currently you have two factor authentication <span class="text-green-500">enabled</span></h2>
|
||||||
<p>To disable two factor authentication, please enter your current password.</p>
|
<p>To disable two factor authentication, please enter your current password.</p>
|
||||||
<form method="POST" action="?/disableTwoFactor" use:removeTwoFactorEnhance data-sveltekit-replacestate>
|
<form method="POST" action="?/disableTotp" use:removeTwoFactorEnhance data-sveltekit-replacestate>
|
||||||
<Form.Field form={removeTwoFactorForm} name="current_password">
|
<Form.Field form={removeTwoFactorForm} name="current_password">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<Form.Label for="password">Current Password</Form.Label>
|
<Form.Label for="password">Current Password</Form.Label>
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<h2>Please scan the following QR Code</h2>
|
<h2>Please scan the following QR Code</h2>
|
||||||
<img src={qrCode} alt="QR Code" />
|
<img src={qrCode} alt="QR Code" />
|
||||||
<form method="POST" action="?/enableTwoFactor" use:addTwoFactorEnhance data-sveltekit-replacestate>
|
<form method="POST" action="?/enableTotp" use:addTwoFactorEnhance data-sveltekit-replacestate>
|
||||||
<Form.Field form={addTwoFactorForm} name="two_factor_code">
|
<Form.Field form={addTwoFactorForm} name="two_factor_code">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<Form.Label for="code">Enter Code</Form.Label>
|
<Form.Label for="code">Enter Code</Form.Label>
|
||||||
|
|
@ -1,31 +1,36 @@
|
||||||
import db from '../../../../../../../db';
|
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { Argon2id } from 'oslo/password';
|
import { Argon2id } from 'oslo/password';
|
||||||
import { alphabet, generateRandomString } from 'oslo/crypto';
|
import { alphabet, generateRandomString } from 'oslo/crypto';
|
||||||
import { redirect } from 'sveltekit-flash-message/server';
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { notSignedInMessage } from '$lib/flashMessages';
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
import type { PageServerLoad } from '../../../$types';
|
import type { PageServerLoad } from '../../../$types';
|
||||||
import {recoveryCodes, twoFactor, usersTable} from '$db/schema';
|
import { recoveryCodesTable, twoFactorTable, usersTable} from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
const dbUser = await db.query.usersTable.findFirst({
|
||||||
where: eq(usersTable.id, user!.id),
|
where: eq(usersTable.id, authedUser.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!dbUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
|
}
|
||||||
|
|
||||||
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
const twoFactorDetails = await db.query.twoFactor.findFirst({
|
||||||
where: eq(twoFactor.userId, dbUser!.id),
|
where: eq(twoFactor.userId, dbUser.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (twoFactorDetails?.enabled) {
|
if (twoFactorDetails?.enabled) {
|
||||||
const dbRecoveryCodes = await db.query.recoveryCodes.findMany({
|
const dbRecoveryCodes = await db.query.recoveryCodes.findMany({
|
||||||
where: eq(recoveryCodes.userId, user!.id),
|
where: eq(recoveryCodes.userId, authedUser.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dbRecoveryCodes.length === 0) {
|
if (dbRecoveryCodes.length === 0) {
|
||||||
|
|
@ -37,7 +42,7 @@ export const load: PageServerLoad = async (event) => {
|
||||||
const hashedCode = await new Argon2id().hash(code);
|
const hashedCode = await new Argon2id().hash(code);
|
||||||
console.log('Inserting recovery code', code, hashedCode);
|
console.log('Inserting recovery code', code, hashedCode);
|
||||||
await db.insert(recoveryCodes).values({
|
await db.insert(recoveryCodes).values({
|
||||||
userId: user!.id,
|
userId: authedUser.id,
|
||||||
code: hashedCode,
|
code: hashedCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -5,22 +5,22 @@ import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||||
import { redirect } from 'sveltekit-flash-message/server';
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
import { Argon2id } from 'oslo/password';
|
import { Argon2id } from 'oslo/password';
|
||||||
import type { PageServerLoad } from '../../../$types';
|
import type { PageServerLoad } from '../../../$types';
|
||||||
import db from '../../../../../../../db';
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { changeUserPasswordSchema } from '$lib/validations/account';
|
import { changeUserPasswordSchema } from '$lib/validations/account';
|
||||||
import { lucia } from '$lib/server/auth.js';
|
import { usersTable } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { usersTable } from '$db/schema';
|
|
||||||
import { notSignedInMessage } from '$lib/flashMessages';
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
import type { Cookie } from 'lucia';
|
import type { Cookie } from 'lucia';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
||||||
|
|
||||||
form.data = {
|
form.data = {
|
||||||
current_password: '',
|
current_password: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
|
@ -34,9 +34,10 @@ export const load: PageServerLoad = async (event) => {
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const { user, session } = locals;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
return fail(401);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
||||||
|
|
@ -57,7 +58,7 @@ export const actions: Actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
const dbUser = await db.query.usersTable.findFirst({
|
||||||
where: eq(usersTable.id, user!.id),
|
where: eq(usersTable.id, authedUser.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (!dbUser?.hashed_password) {
|
// if (!dbUser?.hashed_password) {
|
||||||
|
|
@ -78,14 +79,14 @@ export const actions: Actions = {
|
||||||
if (!currentPasswordVerified) {
|
if (!currentPasswordVerified) {
|
||||||
return setError(form, 'current_password', 'Your password is incorrect');
|
return setError(form, 'current_password', 'Your password is incorrect');
|
||||||
}
|
}
|
||||||
if (user?.username) {
|
if (authedUser?.username) {
|
||||||
let sessionCookie: Cookie;
|
let sessionCookie: Cookie;
|
||||||
try {
|
try {
|
||||||
if (form.data.password !== form.data.confirm_password) {
|
if (form.data.password !== form.data.confirm_password) {
|
||||||
return setError(form, 'Password and confirm password do not match');
|
return setError(form, 'Password and confirm password do not match');
|
||||||
}
|
}
|
||||||
const hashedPassword = await new Argon2id().hash(form.data.password);
|
const hashedPassword = await new Argon2id().hash(form.data.password);
|
||||||
await lucia.invalidateUserSessions(user.id);
|
await lucia.invalidateUserSessions(authedUser.id);
|
||||||
// await db
|
// await db
|
||||||
// .update(usersTable)
|
// .update(usersTable)
|
||||||
// .set({ hashed_password: hashedPassword })
|
// .set({ hashed_password: hashedPassword })
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
<AddToList {in_collection} {in_wishlist} game_id={game.id} {wishlist} {collection} />
|
<AddToList {in_collection} {in_wishlist} game_id={game.id} {wishlist} {collection} />
|
||||||
{:else}
|
{:else}
|
||||||
<span>
|
<span>
|
||||||
<Button href="/sign-up">Sign Up</Button> or <Button href="/login">Sign In</Button> to add to a list.
|
<Button href="/signup">Sign Up</Button> or <Button href="/login">Sign In</Button> to add to a list.
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
{#if $page.url.pathname !== '/login'}
|
{#if $page.url.pathname !== '/login'}
|
||||||
<Button href="/login" variant="ghost">Login</Button>
|
<Button href="/login" variant="ghost">Login</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $page.url.pathname !== '/sign-up'}
|
{#if $page.url.pathname !== '/signup'}
|
||||||
<Button href="/sign-up" variant="ghost">Sign up</Button>
|
<Button href="/signup" variant="ghost">Sign up</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-marketing">
|
<div class="auth-marketing">
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<Card.Title class="text-2xl">Signup for an account</Card.Title>
|
<Card.Title class="text-2xl">Signup for an account</Card.Title>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<form method="POST" action="/sign-up" use:enhance class="grid gap-2 mt-4">
|
<form method="POST" action="/signup" use:enhance class="grid gap-2 mt-4">
|
||||||
<Label for="username">Username <small>(required)</small></Label>
|
<Label for="username">Username <small>(required)</small></Label>
|
||||||
<Input type="text" id="username" class={$errors.username && "outline outline-destructive"} name="username"
|
<Input type="text" id="username" class={$errors.username && "outline outline-destructive"} name="username"
|
||||||
placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} />
|
placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} />
|
||||||
|
|
@ -22,9 +22,10 @@ const config = {
|
||||||
$assets: './src/assets',
|
$assets: './src/assets',
|
||||||
$components: './src/components',
|
$components: './src/components',
|
||||||
'$components/*': 'src/lib/components/*',
|
'$components/*': 'src/lib/components/*',
|
||||||
$db: './src/db',
|
$db: './src/lib/server/api/infrastructure/database',
|
||||||
$server: './src/server',
|
$server: './src/server',
|
||||||
$lib: './src/lib',
|
$lib: './src/lib',
|
||||||
|
$src: './src',
|
||||||
$state: './src/state',
|
$state: './src/state',
|
||||||
$styles: './src/styles',
|
$styles: './src/styles',
|
||||||
$themes: './src/themes',
|
$themes: './src/themes',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue