pull/715/head
Travis Fischer 2025-05-24 22:50:17 +07:00
rodzic 6d6557c720
commit 9dbd7ddd2e
7 zmienionych plików z 96 dodań i 26 usunięć

Wyświetl plik

@ -2,20 +2,19 @@ import { assert, pick } from '@agentic/platform-core'
import { issuer } from '@openauthjs/openauth'
import { GithubProvider } from '@openauthjs/openauth/provider/github'
import { PasswordProvider } from '@openauthjs/openauth/provider/password'
import { MemoryStorage } from '@openauthjs/openauth/storage/memory'
import { PasswordUI } from '@openauthjs/openauth/ui/password'
import { type RawUser } from '@/db'
import { subjects } from '@/lib/auth/subjects'
import { upsertOrLinkUserAccount } from '@/lib/auth/upsert-or-link-user-account'
import { DrizzleAuthStorage } from '@/lib/drizzle-auth-storage'
import { env } from '@/lib/env'
import { getGitHubClient } from '@/lib/external/github'
// Initialize OpenAuth issuer which is a Hono app for all auth routes.
export const authRouter = issuer({
subjects,
storage: MemoryStorage({
persist: './auth-db-temp.json'
}),
storage: DrizzleAuthStorage(),
providers: {
github: GithubProvider({
clientID: env.GITHUB_CLIENT_ID,
@ -25,6 +24,7 @@ export const authRouter = issuer({
password: PasswordProvider(
PasswordUI({
sendCode: async (email, code) => {
// TODO: Send email code to user
// eslint-disable-next-line no-console
console.log({ email, code })
}
@ -97,7 +97,8 @@ export const authRouter = issuer({
accountId: value.email
},
partialUser: {
email: value.email
email: value.email,
emailVerified: true
}
})
} else {

Wyświetl plik

@ -0,0 +1,17 @@
import { jsonb, pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
import { timestamps } from './common'
// Simple key-value store of JSON data for OpenAuth-related state.
export const authData = pgTable('auth_data', {
// Example ID keys:
// "oauth:refresh\u001fuser:f99d3004946f9abb\u001f2cae301e-3fdc-40c4-8cda-83b25a616d06"
// "signing:key\u001ff001a516-838d-4c88-aa9e-719d8fc9d5a3"
// "email\u001ft@t.com\u001fpassword"
// "encryption:key\u001f14d3c324-f9c7-4867-81a9-b0b77b0db0be"
id: text().primaryKey(),
...timestamps,
value: jsonb().$type<Record<string, any>>().notNull(),
expiry: timestamp()
})

Wyświetl plik

@ -1,4 +1,5 @@
export * from './account'
export * from './auth-data'
export * from './common'
export * from './consumer'
export * from './deployment'

Wyświetl plik

@ -0,0 +1,66 @@
import {
joinKey,
splitKey,
type StorageAdapter
} from '@openauthjs/openauth/storage/storage'
import { and, db, eq, isNull, like, lt, or, schema } from '@/db'
export function DrizzleAuthStorage(): StorageAdapter {
return {
async get(key: string[]) {
const id = joinKey(key)
const entry = await db.query.authData.findFirst({
where: eq(schema.authData.id, id)
})
if (!entry) return undefined
if (entry.expiry && Date.now() >= entry.expiry.getTime()) {
await db.delete(schema.authData).where(eq(schema.authData.id, id))
return undefined
}
return entry.value
},
async set(key: string[], value: Record<string, any>, expiry?: Date) {
const id = joinKey(key)
await db
.insert(schema.authData)
.values({
id,
value,
expiry
})
.onConflictDoUpdate({
target: schema.authData.id,
set: {
value,
expiry: expiry ?? null
}
})
},
async remove(key: string[]) {
const id = joinKey(key)
await db.delete(schema.authData).where(eq(schema.authData.id, id))
},
async *scan(prefix: string[]) {
const now = new Date()
const idPrefix = joinKey(prefix)
const entries = await db.query.authData.findMany({
where: and(
like(schema.authData.id, `${idPrefix}%`),
or(isNull(schema.authData.expiry), lt(schema.authData.expiry, now))
)
})
for (const entry of entries) {
yield [splitKey(entry.id), entry.value]
}
}
}
}

Wyświetl plik

@ -32,8 +32,10 @@ app.use(middleware.accessLogger)
app.use(middleware.responseTime)
app.use(middleware.errorHandler)
// Mount all auth routes which are handled by OpenAuth
app.route('', authRouter)
// Mount all v1 API routes
app.route('/v1', apiV1)
app.doc31('/docs', {

Wyświetl plik

@ -31,11 +31,8 @@ async function main() {
if (authSession) {
try {
await client.setRefreshAuthToken(authSession.refreshToken)
} catch (err: any) {
console.warn(
'Existing auth session is invalid; logging out...',
err.message
)
} catch {
console.warn('Existing auth session is invalid; logging out.\n')
AuthStore.clearAuth()
}
}

Wyświetl plik

@ -13,24 +13,10 @@
- like https://github.com/tierrun/tier and Saasify
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
- https://blog.tier.run/tier-hello-world-demo
- auth
- decide on approach for auth
- built-in, first-party, tight coupling
- https://www.better-auth.com
- issues
- doesn't allow dynamic social provider config
- awkward schema cli generation and constraints
- awkward cookie-only session support (need JWTs for CLI and SDK)
- client uses dynamic proxy for methods which makes DX awkward
- should be able to use custom `ky`-based client
- drizzle pg adapter requires `Date` timestamps instead of default strings
- https://github.com/toolbeam/openauth
- https://github.com/aipotheosis-labs/aci/tree/main/backend/apps
- https://github.com/NangoHQ/nango
- https://github.com/transitive-bullshit?submit=Search&q=oauth&tab=stars&type=&sort=&direction=&submit=Search
- clerk / workos / auth0
- consider switching to [consola](https://github.com/unjs/consola) for logging?
- consider switching to `bun` (for `--hot` reloading!!)
- transactional emails
- openauth password emails and `sendCode`
## License