mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-30 06:41:18 +01:00
feat(backend): add DB persistence layer (#79)
* feat(backend): add DB persistence layer Replace raw pg Pool with Drizzle ORM backed by Bun.sql. Add per-user source configuration table (user_sources). Migrate Better Auth to drizzle-adapter. Add AES-256-GCM credential encryption. Co-authored-by: Ona <no-reply@ona.com> * fix(backend): set updatedAt explicitly in all mutations onConflictDoUpdate bypasses Drizzle's $onUpdate hook. Set updatedAt explicitly in all mutation methods. Co-authored-by: Ona <no-reply@ona.com> * fix(backend): add composite index on user_sources Add (user_id, enabled) index for the enabled() query path. Co-authored-by: Ona <no-reply@ona.com> --------- Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { Hono } from "hono"
|
||||
|
||||
import { auth } from "./index.ts"
|
||||
import type { Auth } from "./index.ts"
|
||||
|
||||
export function registerAuthHandlers(app: Hono): void {
|
||||
export function registerAuthHandlers(app: Hono, auth: Auth): void {
|
||||
app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { betterAuth } from "better-auth"
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
||||
|
||||
import { pool } from "../db.ts"
|
||||
import type { Database } from "../db/index.ts"
|
||||
import * as schema from "../db/schema.ts"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: pool,
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
})
|
||||
export function createAuth(db: Database) {
|
||||
return betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema,
|
||||
}),
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type Auth = ReturnType<typeof createAuth>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Context, MiddlewareHandler, Next } from "hono"
|
||||
|
||||
import type { Auth } from "./index.ts"
|
||||
import type { AuthSession, AuthUser } from "./session.ts"
|
||||
|
||||
import { auth } from "./index.ts"
|
||||
|
||||
export interface SessionVariables {
|
||||
user: AuthUser | null
|
||||
session: AuthSession | null
|
||||
@@ -18,46 +17,52 @@ declare module "hono" {
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that attaches session and user to the context.
|
||||
* Does not reject unauthenticated requests - use requireSession for that.
|
||||
* Creates a middleware that attaches session and user to the context.
|
||||
* Does not reject unauthenticated requests - use createRequireSession for that.
|
||||
*/
|
||||
export async function sessionMiddleware(c: Context, next: Next): Promise<void> {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers })
|
||||
export function createSessionMiddleware(auth: Auth): AuthSessionMiddleware {
|
||||
return async (c: Context, next: Next): Promise<void> => {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers })
|
||||
|
||||
if (session) {
|
||||
c.set("user", session.user)
|
||||
c.set("session", session.session)
|
||||
} else {
|
||||
c.set("user", null)
|
||||
c.set("session", null)
|
||||
}
|
||||
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a middleware that requires a valid session. Returns 401 if not authenticated.
|
||||
*/
|
||||
export function createRequireSession(auth: Auth): AuthSessionMiddleware {
|
||||
return async (c: Context, next: Next): Promise<Response | void> => {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers })
|
||||
|
||||
if (!session) {
|
||||
return c.json({ error: "Unauthorized" }, 401)
|
||||
}
|
||||
|
||||
if (session) {
|
||||
c.set("user", session.user)
|
||||
c.set("session", session.session)
|
||||
} else {
|
||||
c.set("user", null)
|
||||
c.set("session", null)
|
||||
await next()
|
||||
}
|
||||
|
||||
await next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that requires a valid session. Returns 401 if not authenticated.
|
||||
* Creates a function to get session from headers. Useful for WebSocket upgrade validation.
|
||||
*/
|
||||
export async function requireSession(c: Context, next: Next): Promise<Response | void> {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers })
|
||||
|
||||
if (!session) {
|
||||
return c.json({ error: "Unauthorized" }, 401)
|
||||
export function createGetSessionFromHeaders(auth: Auth) {
|
||||
return async (
|
||||
headers: Headers,
|
||||
): Promise<{ user: AuthUser; session: AuthSession } | null> => {
|
||||
const session = await auth.api.getSession({ headers })
|
||||
return session
|
||||
}
|
||||
|
||||
c.set("user", session.user)
|
||||
c.set("session", session.session)
|
||||
await next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session from headers. Useful for WebSocket upgrade validation.
|
||||
*/
|
||||
export async function getSessionFromHeaders(
|
||||
headers: Headers,
|
||||
): Promise<{ user: AuthUser; session: AuthSession } | null> {
|
||||
const session = await auth.api.getSession({ headers })
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { auth } from "./index.ts"
|
||||
import type { Auth } from "./index.ts"
|
||||
|
||||
export type AuthUser = typeof auth.$Infer.Session.user
|
||||
export type AuthSession = typeof auth.$Infer.Session.session
|
||||
export type AuthUser = Auth["$Infer"]["Session"]["user"]
|
||||
export type AuthSession = Auth["$Infer"]["Session"]["session"]
|
||||
|
||||
Reference in New Issue
Block a user