diff --git a/packages/studioCMS/package.json b/packages/studioCMS/package.json index 66a186cf9..796f0da13 100644 --- a/packages/studioCMS/package.json +++ b/packages/studioCMS/package.json @@ -46,6 +46,7 @@ "@inox-tools/sitemap-ext": "^0.2.11", "@lucia-auth/adapter-drizzle": "^1.0.7", "@markdoc/markdoc": "^0.4.0", + "@noble/hashes": "^1.4.0", "@shikijs/transformers": "^1.4.0", "@unocss/astro": "^0.59.4", "@unocss/reset": "^0.59.4", diff --git a/packages/studioCMS/src/db/tables.ts b/packages/studioCMS/src/db/tables.ts index c261992e4..f40f010b0 100644 --- a/packages/studioCMS/src/db/tables.ts +++ b/packages/studioCMS/src/db/tables.ts @@ -1,17 +1,18 @@ // @ts-expect-error - This is a missing type definition for the `astro:db` import since its a virtual module during Astro Runtime -import { NOW, column, defineTable } from 'astro:db'; +import { NOW, column, defineTable, sql } from 'astro:db'; +import { randomUUID } from 'crypto'; export const Session = defineTable({ columns: { id: column.text({ primaryKey: true, nullable: false }), - userId: column.number({ references: () => User.columns.id, nullable: false }), + userId: column.text({ references: () => User.columns.id, nullable: false }), expiresAt: column.number({ nullable: false }), }, }); export const User = defineTable({ columns: { - id: column.number({ primaryKey: true }), + id: column.text({ primaryKey: true, default: randomUUID() }), url: column.text({ optional: true }), name: column.text(), email: column.text({ unique: true, optional: true }), diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/lib/auth.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/lib/auth.ts index b8ac4bd83..8072de5fc 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/lib/auth.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/lib/auth.ts @@ -24,8 +24,7 @@ export const lucia = new Lucia(adapter, { getUserAttributes: (attributes) => { return { // attributes has the type of DatabaseUserAttributes - githubId: attributes.githubId, - discordId: attributes.discordId, + id: attributes.id, username: attributes.username, }; }, @@ -39,7 +38,6 @@ declare module 'lucia' { } interface DatabaseUserAttributes { - githubId: number; - discordId: string; username: string; + id: string; } diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/middleware/index.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/middleware/index.ts index 334bc4276..de99fdb1e 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/middleware/index.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/middleware/index.ts @@ -42,7 +42,7 @@ export const onRequest = defineMiddleware(async (context, next) => { const [dbUser] = await db .select() .from(User) - .where(eq(User.id, Number(user.id))); + .where(eq(User.id, user.id)); context.locals.dbUser = dbUser; context.locals.isLoggedIn = true; diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/login.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/login.ts index e68a277dc..25c99418b 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/login.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/login.ts @@ -1,7 +1,7 @@ // @ts-expect-error - Some types can only be imported from the Astro runtime import { User, db, eq } from 'astro:db'; import { lucia } from "studiocms-dashboard:auth"; -import { Argon2id } from "oslo/password"; +import { scryptAsync } from "@noble/hashes/scrypt"; import type { APIContext } from "astro"; @@ -38,7 +38,11 @@ export async function POST(context: APIContext): Promise { ); } - const validPassword = await new Argon2id().verify(existingUser.password, password); + + const hashedPassword = await scryptAsync(password, existingUser.id, { N: 2 ** 12, r: 8, p: 1, dkLen: 32 }) + const hashedPasswordString = Buffer.from(hashedPassword.buffer).toString(); + const validPassword = hashedPasswordString === existingUser.password; + if (!validPassword) { return new Response( JSON.stringify({ @@ -50,7 +54,7 @@ export async function POST(context: APIContext): Promise { ); } - const session = await lucia.createSession(existingUser.id.toString(), {}); + const session = await lucia.createSession(existingUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/register.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/register.ts index c0bdb0020..7dc055d4c 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/register.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/api/register.ts @@ -1,7 +1,7 @@ // @ts-expect-error - Some types can only be imported from the Astro runtime import { User, db, eq } from 'astro:db'; import { lucia } from "studiocms-dashboard:auth"; -import { Argon2id } from "oslo/password"; +import { scryptAsync } from "@noble/hashes/scrypt"; import type { APIContext } from "astro"; @@ -37,7 +37,6 @@ export async function POST(context: APIContext): Promise { ); } - const hashedPassword = await new Argon2id().hash(password); const name = formData.get("displayname"); const existingUser = await db.select().from(User).where(eq(User.username, username)).get() @@ -58,11 +57,19 @@ export async function POST(context: APIContext): Promise { .values({ name: name as string, username, - password: hashedPassword, }) const newUser = await db.select().from(User).where(eq(User.username, username)).get(); - const session = await lucia.createSession(newUser.id.toString(), {}); + const hashedPassword = await scryptAsync(password, newUser.id, { N: 2 ** 12, r: 8, p: 1, dkLen: 32 }) + const hashedPasswordString = Buffer.from(hashedPassword.buffer).toString(); + await db + .update(User) + .set({ + password: hashedPasswordString + }) + .where(eq(User.id, newUser.id)) + + const session = await lucia.createSession(newUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/auth0/callback.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/auth0/callback.ts index e293ebda1..8929bb115 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/auth0/callback.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/auth0/callback.ts @@ -69,7 +69,7 @@ const clientDomain = `https://${NoHTTPDOMAIN}`; const existingUser = await db.select().from(User).where(eq(User.auth0Id, auth0Id)).get(); if (existingUser) { - const session = await lucia.createSession(existingUser.id.toString(), {}); + const session = await lucia.createSession(existingUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return redirect(await urlGenFactory(true, undefined, dashboardRouteOverride)); @@ -95,7 +95,7 @@ const clientDomain = `https://${NoHTTPDOMAIN}`; .returning() .get(); - const session = await lucia.createSession(createdUser.id.toString(), {}); + const session = await lucia.createSession(createdUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/discord/callback.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/discord/callback.ts index 21d674d72..a31f96af8 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/discord/callback.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/discord/callback.ts @@ -65,7 +65,7 @@ export async function GET(context: APIContext): Promise { const existingUserById = await db.select().from(User).where(eq(User.discordId, discordId)).get(); if (existingUserById) { - const session = await lucia.createSession(existingUserById.id.toString(), {}); + const session = await lucia.createSession(existingUserById.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return redirect(await urlGenFactory(true, undefined, dashboardRouteOverride)); @@ -91,7 +91,7 @@ export async function GET(context: APIContext): Promise { .returning() .get(); - const session = await lucia.createSession(createdUser.id.toString(), {}); + const session = await lucia.createSession(createdUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/github/callback.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/github/callback.ts index c4c59bab3..3e9eb1c0a 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/github/callback.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/github/callback.ts @@ -60,7 +60,7 @@ export async function GET(context: APIContext): Promise { const existingUser = await db.select().from(User).where(eq(User.githubId, githubId)).get(); if (existingUser) { - const session = await lucia.createSession(existingUser.id.toString(), {}); + const session = await lucia.createSession(existingUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return redirect(await urlGenFactory(true, undefined, dashboardRouteOverride)); @@ -87,7 +87,7 @@ export async function GET(context: APIContext): Promise { .returning() .get(); - const session = await lucia.createSession(createdUser.id.toString(), {}); + const session = await lucia.createSession(createdUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); diff --git a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/google/callback.ts b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/google/callback.ts index e36c149d5..279cbdca5 100644 --- a/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/google/callback.ts +++ b/packages/studioCMS/src/integrations/studioCMSDashboard/routes/authroutes/login/google/callback.ts @@ -59,7 +59,7 @@ export async function GET(context: APIContext): Promise { const existingUser = await db.select().from(User).where(eq(User.googleId, googleId)).get(); if (existingUser) { - const session = await lucia.createSession(existingUser.id.toString(), {}); + const session = await lucia.createSession(existingUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return redirect(await urlGenFactory(true, undefined, dashboardRouteOverride)); @@ -89,7 +89,7 @@ export async function GET(context: APIContext): Promise { .returning() .get(); - const session = await lucia.createSession(createdUser.id.toString(), {}); + const session = await lucia.createSession(createdUser.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); diff --git a/packages/studioCMS/src/schemas/auth.ts b/packages/studioCMS/src/schemas/auth.ts index 18054d1b1..6f2014eb5 100644 --- a/packages/studioCMS/src/schemas/auth.ts +++ b/packages/studioCMS/src/schemas/auth.ts @@ -49,7 +49,7 @@ export const authProviderSchema = z.object({ * Username and Password Auth Provider - Powered by Lucia * */ - usernameAndPassword: z.boolean().optional().default(false), + usernameAndPassword: z.boolean().optional().default(true), usernameAndPasswordConfig: localUsernameAndPasswordConfig, }).optional().default({}); diff --git a/packages/studioCMS/virt-db.d.ts b/packages/studioCMS/virt-db.d.ts index b80c3b2d2..fd951a0e7 100644 --- a/packages/studioCMS/virt-db.d.ts +++ b/packages/studioCMS/virt-db.d.ts @@ -14,11 +14,11 @@ declare module 'astro:db' { >; export const Session: import("@astrojs/db/runtime").Table< "Session", - {"id":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"id","collection":"Session","primaryKey":true}},"userId":{"type":"number","schema":{"unique":false,"deprecated":false,"name":"userId","collection":"Session","primaryKey":false,"optional":false,"references":{"type":"number","schema":{"unique":false,"deprecated":false,"name":"id","collection":"User","primaryKey":true}}}},"expiresAt":{"type":"number","schema":{"unique":false,"deprecated":false,"name":"expiresAt","collection":"Session","primaryKey":false,"optional":false}}} + {"id":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"id","collection":"Session","primaryKey":true}},"userId":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"userId","collection":"Session","primaryKey":false,"optional":false,"references":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"id","collection":"User","default":"a49fd57c-5e15-4b27-b273-ea430df24b2f","primaryKey":true}}}},"expiresAt":{"type":"number","schema":{"unique":false,"deprecated":false,"name":"expiresAt","collection":"Session","primaryKey":false,"optional":false}}} >; export const User: import("@astrojs/db/runtime").Table< "User", - {"id":{"type":"number","schema":{"unique":false,"deprecated":false,"name":"id","collection":"User","primaryKey":true}},"url":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"url","collection":"User","primaryKey":false,"optional":true}},"name":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"name","collection":"User","primaryKey":false,"optional":false}},"email":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"email","collection":"User","primaryKey":false,"optional":true}},"avatar":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"avatar","collection":"User","primaryKey":false,"optional":true}},"githubId":{"type":"number","schema":{"unique":true,"deprecated":false,"name":"githubId","collection":"User","primaryKey":false,"optional":true}},"githubURL":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"githubURL","collection":"User","primaryKey":false,"optional":true}},"discordId":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"discordId","collection":"User","primaryKey":false,"optional":true}},"googleId":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"googleId","collection":"User","primaryKey":false,"optional":true}},"auth0Id":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"auth0Id","collection":"User","primaryKey":false,"optional":true}},"username":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"username","collection":"User","primaryKey":false,"optional":false}},"password":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"password","collection":"User","primaryKey":false,"optional":true}},"updatedAt":{"type":"date","schema":{"optional":false,"unique":false,"deprecated":false,"name":"updatedAt","collection":"User","default":{"__serializedSQL":true,"sql":"CURRENT_TIMESTAMP"}}},"createdAt":{"type":"date","schema":{"optional":false,"unique":false,"deprecated":false,"name":"createdAt","collection":"User","default":{"__serializedSQL":true,"sql":"CURRENT_TIMESTAMP"}}}} + {"id":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"id","collection":"User","default":"a49fd57c-5e15-4b27-b273-ea430df24b2f","primaryKey":true}},"url":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"url","collection":"User","primaryKey":false,"optional":true}},"name":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"name","collection":"User","primaryKey":false,"optional":false}},"email":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"email","collection":"User","primaryKey":false,"optional":true}},"avatar":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"avatar","collection":"User","primaryKey":false,"optional":true}},"githubId":{"type":"number","schema":{"unique":true,"deprecated":false,"name":"githubId","collection":"User","primaryKey":false,"optional":true}},"githubURL":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"githubURL","collection":"User","primaryKey":false,"optional":true}},"discordId":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"discordId","collection":"User","primaryKey":false,"optional":true}},"googleId":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"googleId","collection":"User","primaryKey":false,"optional":true}},"auth0Id":{"type":"text","schema":{"unique":true,"deprecated":false,"name":"auth0Id","collection":"User","primaryKey":false,"optional":true}},"username":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"username","collection":"User","primaryKey":false,"optional":false}},"password":{"type":"text","schema":{"unique":false,"deprecated":false,"name":"password","collection":"User","primaryKey":false,"optional":true}},"updatedAt":{"type":"date","schema":{"optional":false,"unique":false,"deprecated":false,"name":"updatedAt","collection":"User","default":{"__serializedSQL":true,"sql":"CURRENT_TIMESTAMP"}}},"createdAt":{"type":"date","schema":{"optional":false,"unique":false,"deprecated":false,"name":"createdAt","collection":"User","default":{"__serializedSQL":true,"sql":"CURRENT_TIMESTAMP"}}}} >; export const Permissions: import("@astrojs/db/runtime").Table< "Permissions", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0da8f7d12..285ad34e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: '@markdoc/markdoc': specifier: ^0.4.0 version: 0.4.0 + '@noble/hashes': + specifier: ^1.4.0 + version: 1.4.0 '@shikijs/transformers': specifier: ^1.4.0 version: 1.4.0 @@ -2792,6 +2795,11 @@ packages: resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} dev: false + /@noble/hashes@1.4.0: + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + dev: false + /@node-rs/argon2-android-arm-eabi@1.7.0: resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} engines: {node: '>= 10'}