kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: move to openauth
rodzic
5b23dd92ed
commit
ecd8f803bc
|
@ -42,3 +42,5 @@ next-env.d.ts
|
|||
|
||||
old/
|
||||
out/
|
||||
|
||||
apps/api/auth-db-temp.json
|
||||
|
|
|
@ -33,14 +33,15 @@
|
|||
"@hono/node-server": "^1.14.1",
|
||||
"@hono/sentry": "^1.2.1",
|
||||
"@hono/zod-openapi": "^0.19.6",
|
||||
"@openauthjs/openauth": "^0.4.3",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@redocly/openapi-core": "^1.34.3",
|
||||
"@sentry/node": "^9.19.0",
|
||||
"better-auth": "^1.2.8",
|
||||
"eventid": "^2.0.1",
|
||||
"exit-hook": "catalog:",
|
||||
"hono": "^4.7.9",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"octokit": "^5.0.2",
|
||||
"p-all": "^5.0.0",
|
||||
"parse-json": "^8.3.0",
|
||||
"postgres": "^3.4.5",
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { fromError } from 'zod-validation-error'
|
||||
|
||||
import type { AuthenticatedEnv, DefaultContext } from '@/lib/types'
|
||||
import { auth } from '@/lib/auth'
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import * as middleware from '@/lib/middleware'
|
||||
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
|
||||
|
||||
|
@ -105,16 +104,6 @@ registerV1AdminConsumersGetConsumerByToken(privateRouter)
|
|||
// Webhook event handlers
|
||||
registerV1StripeWebhook(publicRouter)
|
||||
|
||||
// Better-Auth Handler for all auth-related routes
|
||||
apiV1.on(['POST', 'GET'], 'auth/**', async (c: DefaultContext) => {
|
||||
const logger = c.get('logger')
|
||||
logger.info(c.req.method, c.req.url, c.req.header())
|
||||
|
||||
const res = await auth.handler(c.req.raw)
|
||||
logger.info('auth result', res)
|
||||
return res
|
||||
})
|
||||
|
||||
// Setup routes and middleware
|
||||
apiV1.route('/', publicRouter)
|
||||
apiV1.use(middleware.authenticate)
|
||||
|
|
|
@ -4,7 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
|||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import { db, schema } from '@/db'
|
||||
import { ensureAuthUser } from '@/lib/ensure-auth-user'
|
||||
import { ensureUniqueTeamSlug } from '@/lib/ensure-unique-team-slug'
|
||||
import { ensureUniqueNamespace } from '@/lib/ensure-unique-namespace'
|
||||
import {
|
||||
openapiAuthenticatedSecuritySchemas,
|
||||
openapiErrorResponses
|
||||
|
@ -45,7 +45,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono<AuthenticatedEnv>) {
|
|||
const user = await ensureAuthUser(c)
|
||||
const body = c.req.valid('json')
|
||||
|
||||
await ensureUniqueTeamSlug(body.slug)
|
||||
await ensureUniqueNamespace(body.slug, { label: 'Team slug' })
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const [team] = await tx
|
||||
|
|
|
@ -5,7 +5,7 @@ import { assert, HttpError } from '@agentic/platform-core'
|
|||
import { and, db, eq, schema } from '@/db'
|
||||
import { setConsumerStripeSubscriptionStatus } from '@/lib/consumers/utils'
|
||||
import { env, isStripeLive } from '@/lib/env'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { stripe } from '@/lib/external/stripe'
|
||||
|
||||
const relevantStripeEvents = new Set<Stripe.Event.Type>([
|
||||
'customer.subscription.updated'
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
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 { env } from '@/lib/env'
|
||||
import { getGitHubClient } from '@/lib/external/github'
|
||||
|
||||
export const authRouter = issuer({
|
||||
subjects,
|
||||
storage: MemoryStorage({
|
||||
persist: './auth-db-temp.json'
|
||||
}),
|
||||
providers: {
|
||||
github: GithubProvider({
|
||||
clientID: env.GITHUB_CLIENT_ID,
|
||||
clientSecret: env.GITHUB_CLIENT_SECRET,
|
||||
scopes: ['user:email']
|
||||
}),
|
||||
password: PasswordProvider(
|
||||
PasswordUI({
|
||||
sendCode: async (email, code) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log({ email, code })
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
success: async (ctx, value) => {
|
||||
const { provider } = value
|
||||
let user: RawUser | undefined
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Auth success', provider, ctx, JSON.stringify(value, null, 2))
|
||||
|
||||
function getPartialOAuthAccount() {
|
||||
assert(provider === 'github', `Unsupported provider "${provider}"`)
|
||||
|
||||
return {
|
||||
provider,
|
||||
accessToken: value.tokenset.access,
|
||||
refreshToken: value.tokenset.refresh,
|
||||
// `expires_in` and `refresh_token_expires_in` are given in seconds
|
||||
accessTokenExpiresAt: new Date(
|
||||
Date.now() + value.tokenset.raw.expires_in * 1000
|
||||
),
|
||||
refreshTokenExpiresAt: new Date(
|
||||
Date.now() + value.tokenset.raw.refresh_token_expires_in * 1000
|
||||
),
|
||||
scope: (value.tokenset.raw.scope as string) || undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (provider === 'github') {
|
||||
const client = getGitHubClient({
|
||||
accessToken: value.tokenset.access
|
||||
})
|
||||
const { data: ghUser } = await client.rest.users.getAuthenticated()
|
||||
|
||||
if (!ghUser.email) {
|
||||
const { data: emails } = await client.request('GET /user/emails')
|
||||
const primary = emails.find((e) => e.primary)
|
||||
const verified = emails.find((e) => e.verified)
|
||||
const fallback = emails.find((e) => e.email)
|
||||
const email = primary?.email || verified?.email || fallback?.email
|
||||
ghUser.email = email!
|
||||
}
|
||||
|
||||
assert(
|
||||
ghUser.email,
|
||||
'Error authenticating with GitHub: user email is required.'
|
||||
)
|
||||
|
||||
user = await upsertOrLinkUserAccount({
|
||||
partialAccount: {
|
||||
accountId: `${ghUser.id}`,
|
||||
accountUsername: ghUser.login.toLowerCase(),
|
||||
...getPartialOAuthAccount()
|
||||
},
|
||||
partialUser: {
|
||||
email: ghUser.email,
|
||||
emailVerified: true,
|
||||
name: ghUser.name || undefined,
|
||||
username: ghUser.login.toLowerCase(),
|
||||
image: ghUser.avatar_url
|
||||
}
|
||||
})
|
||||
} else if (provider === 'password') {
|
||||
user = await upsertOrLinkUserAccount({
|
||||
partialAccount: {
|
||||
provider,
|
||||
accountId: value.email
|
||||
},
|
||||
partialUser: {
|
||||
email: value.email
|
||||
}
|
||||
})
|
||||
} else {
|
||||
assert(
|
||||
user,
|
||||
404,
|
||||
`Authentication error: unsupported provider "${provider}"`
|
||||
)
|
||||
}
|
||||
|
||||
assert(
|
||||
user,
|
||||
404,
|
||||
`Authentication error for provider "${provider}": User not found`
|
||||
)
|
||||
return ctx.subject('user', pick(user, 'id'))
|
||||
}
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
import { relations } from '@fisch0920/drizzle-orm'
|
||||
import { index, pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
|
||||
|
||||
import { userIdSchema } from '../schemas'
|
||||
import {
|
||||
accountPrimaryId,
|
||||
authProviderTypeEnum,
|
||||
createSelectSchema,
|
||||
timestamps,
|
||||
userId
|
||||
} from './common'
|
||||
import { users } from './user'
|
||||
|
||||
export const accounts = pgTable(
|
||||
'accounts',
|
||||
{
|
||||
...accountPrimaryId,
|
||||
...timestamps,
|
||||
|
||||
userId: userId()
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
provider: authProviderTypeEnum().notNull(),
|
||||
|
||||
/** Provider-specific account ID (or email in the case of `password` provider) */
|
||||
accountId: text().notNull(),
|
||||
|
||||
/** Provider-specific username */
|
||||
accountUsername: text(),
|
||||
|
||||
/** Standard OAuth2 access token */
|
||||
accessToken: text(),
|
||||
|
||||
/** Standard OAuth2 refresh token */
|
||||
refreshToken: text(),
|
||||
|
||||
/** Standard OAuth2 access token expires at */
|
||||
accessTokenExpiresAt: timestamp(),
|
||||
|
||||
/** Standard OAuth2 refresh token expires at */
|
||||
refreshTokenExpiresAt: timestamp(),
|
||||
|
||||
/** OAuth scope(s) */
|
||||
scope: text()
|
||||
},
|
||||
(table) => [
|
||||
index('account_provider_idx').on(table.provider),
|
||||
index('account_userId_idx').on(table.userId),
|
||||
index('account_createdAt_idx').on(table.createdAt),
|
||||
index('account_updatedAt_idx').on(table.updatedAt),
|
||||
index('account_deletedAt_idx').on(table.deletedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const accountsRelations = relations(accounts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [accounts.userId],
|
||||
references: [users.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export const accountSelectSchema = createSelectSchema(accounts, {
|
||||
userId: userIdSchema
|
||||
})
|
||||
.omit({
|
||||
accessToken: true,
|
||||
refreshToken: true,
|
||||
accessTokenExpiresAt: true,
|
||||
refreshTokenExpiresAt: true
|
||||
})
|
||||
.strip()
|
||||
.openapi('Account')
|
|
@ -1,53 +0,0 @@
|
|||
import { pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
|
||||
|
||||
import {
|
||||
accountPrimaryId,
|
||||
authTimestamps,
|
||||
sessionPrimaryId,
|
||||
userId,
|
||||
verificationPrimaryId
|
||||
} from './common'
|
||||
import { users } from './user'
|
||||
|
||||
// These tables are all managed by better-auth.
|
||||
|
||||
export const sessions = pgTable('sessions', {
|
||||
...sessionPrimaryId,
|
||||
...authTimestamps,
|
||||
|
||||
token: text().notNull().unique(),
|
||||
expiresAt: timestamp({ mode: 'date' }).notNull(),
|
||||
ipAddress: text(),
|
||||
userAgent: text(),
|
||||
userId: userId()
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
})
|
||||
|
||||
export const accounts = pgTable('accounts', {
|
||||
...accountPrimaryId,
|
||||
...authTimestamps,
|
||||
|
||||
accountId: text().notNull(),
|
||||
providerId: text().notNull(),
|
||||
userId: userId()
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
accessToken: text(),
|
||||
refreshToken: text(),
|
||||
accessTokenExpiresAt: timestamp({ mode: 'date' }),
|
||||
refreshTokenExpiresAt: timestamp({ mode: 'date' }),
|
||||
scope: text(),
|
||||
idToken: text(),
|
||||
password: text()
|
||||
})
|
||||
|
||||
export const verifications = pgTable('verifications', {
|
||||
...verificationPrimaryId,
|
||||
...authTimestamps,
|
||||
|
||||
identifier: text().notNull(),
|
||||
value: text().notNull(),
|
||||
|
||||
expiresAt: timestamp({ mode: 'date' }).notNull()
|
||||
})
|
|
@ -14,7 +14,7 @@ import { createSchemaFactory } from '@fisch0920/drizzle-zod'
|
|||
import { z } from '@hono/zod-openapi'
|
||||
import { createId as createCuid2 } from '@paralleldrive/cuid2'
|
||||
|
||||
const usernameAndTeamSlugLength = 64 as const
|
||||
export const namespaceMaxLength = 64 as const
|
||||
|
||||
// prefix is max 5 characters
|
||||
// separator is 1 character
|
||||
|
@ -29,18 +29,10 @@ export const idPrefixMap = {
|
|||
consumer: 'csmr',
|
||||
logEntry: 'log',
|
||||
|
||||
// better-auth
|
||||
// auth
|
||||
user: 'user',
|
||||
account: 'acct',
|
||||
session: 'sess',
|
||||
verification: 'veri',
|
||||
'rate-limit': 'ratel',
|
||||
organization: 'org',
|
||||
member: 'mem',
|
||||
invitation: 'inv',
|
||||
jwks: 'jwks',
|
||||
passkey: 'passk',
|
||||
'two-factor': '2fa'
|
||||
session: 'sess'
|
||||
} as const
|
||||
|
||||
export type ModelType = keyof typeof idPrefixMap
|
||||
|
@ -69,9 +61,7 @@ export const consumerPrimaryId = getPrimaryId('consumer')
|
|||
export const logEntryPrimaryId = getPrimaryId('logEntry')
|
||||
export const teamPrimaryId = getPrimaryId('team')
|
||||
export const userPrimaryId = getPrimaryId('user')
|
||||
export const sessionPrimaryId = getPrimaryId('session')
|
||||
export const accountPrimaryId = getPrimaryId('account')
|
||||
export const verificationPrimaryId = getPrimaryId('verification')
|
||||
|
||||
/**
|
||||
* All of our model primary ids have the following format:
|
||||
|
@ -123,14 +113,14 @@ export function deploymentIdentifier<
|
|||
|
||||
export function username<U extends string, T extends Readonly<[U, ...U[]]>>(
|
||||
config?: PgVarcharConfig<T | Writable<T>, never>
|
||||
): PgVarcharBuilderInitial<'', Writable<T>, typeof usernameAndTeamSlugLength> {
|
||||
return varchar({ length: usernameAndTeamSlugLength, ...config })
|
||||
): PgVarcharBuilderInitial<'', Writable<T>, typeof namespaceMaxLength> {
|
||||
return varchar({ length: namespaceMaxLength, ...config })
|
||||
}
|
||||
|
||||
export function teamSlug<U extends string, T extends Readonly<[U, ...U[]]>>(
|
||||
config?: PgVarcharConfig<T | Writable<T>, never>
|
||||
): PgVarcharBuilderInitial<'', Writable<T>, typeof usernameAndTeamSlugLength> {
|
||||
return varchar({ length: usernameAndTeamSlugLength, ...config })
|
||||
): PgVarcharBuilderInitial<'', Writable<T>, typeof namespaceMaxLength> {
|
||||
return varchar({ length: namespaceMaxLength, ...config })
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,15 +147,6 @@ export const timestamps = {
|
|||
deletedAt: timestamp()
|
||||
}
|
||||
|
||||
export const authTimestamps = {
|
||||
createdAt: timestamp({ mode: 'date' })
|
||||
.notNull()
|
||||
.$defaultFn(() => /* @__PURE__ */ new Date()),
|
||||
updatedAt: timestamp({ mode: 'date' })
|
||||
.notNull()
|
||||
.$defaultFn(() => /* @__PURE__ */ new Date())
|
||||
}
|
||||
|
||||
export const userRoleEnum = pgEnum('UserRole', ['user', 'admin'])
|
||||
export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin'])
|
||||
export const logEntryTypeEnum = pgEnum('LogEntryType', ['log'])
|
||||
|
@ -183,6 +164,10 @@ export const pricingIntervalEnum = pgEnum('PricingInterval', [
|
|||
'year'
|
||||
])
|
||||
export const pricingCurrencyEnum = pgEnum('PricingCurrency', ['usd'])
|
||||
export const authProviderTypeEnum = pgEnum('AuthProviderType', [
|
||||
'github',
|
||||
'password'
|
||||
])
|
||||
|
||||
export const { createInsertSchema, createSelectSchema, createUpdateSchema } =
|
||||
createSchemaFactory({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from './auth'
|
||||
export * from './account'
|
||||
export * from './common'
|
||||
export * from './consumer'
|
||||
export * from './deployment'
|
||||
|
|
|
@ -1,226 +0,0 @@
|
|||
|
||||
// TODO: Currently unused after forking @fisch0920/drizzle-zod.
|
||||
export function makeNullablePropsOptional<Schema extends z.AnyZodObject>(
|
||||
schema: Schema
|
||||
): z.ZodObject<{
|
||||
[key in keyof Schema['shape']]: Schema['shape'][key] extends z.ZodNullable<
|
||||
infer T
|
||||
>
|
||||
? z.ZodOptional<T>
|
||||
: Schema['shape'][key]
|
||||
}> {
|
||||
const entries = Object.entries(schema.shape)
|
||||
const newProps: any = {}
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
newProps[key] =
|
||||
value instanceof z.ZodNullable ? value.unwrap().optional() : value
|
||||
return newProps
|
||||
}
|
||||
|
||||
return z.object(newProps) as any
|
||||
}
|
||||
|
||||
export type ColumnType =
|
||||
// string
|
||||
| 'text'
|
||||
| 'varchar'
|
||||
| 'timestamp'
|
||||
| 'stripeId'
|
||||
| 'projectId'
|
||||
| 'deploymentId'
|
||||
| 'cuid'
|
||||
// boolean
|
||||
| 'boolean'
|
||||
// number
|
||||
| 'integer'
|
||||
| 'smallint'
|
||||
| 'bigint'
|
||||
// json
|
||||
| 'json'
|
||||
| 'jsonb'
|
||||
|
||||
export type ColumnTypeToTSType<T extends ColumnType> = T extends
|
||||
| 'text'
|
||||
| 'varchar'
|
||||
| 'timestamp'
|
||||
| 'cuid'
|
||||
| 'stripeId'
|
||||
| 'projectId'
|
||||
| 'deploymentId'
|
||||
? string
|
||||
: T extends 'boolean'
|
||||
? boolean
|
||||
: T extends 'integer' | 'smallint' | 'bigint'
|
||||
? number
|
||||
: never
|
||||
|
||||
/**
|
||||
* @see https://github.com/drizzle-team/@fisch0920/drizzle-orm/issues/2745
|
||||
*/
|
||||
function optional<
|
||||
T extends ColumnType,
|
||||
InferredType extends
|
||||
| string
|
||||
| boolean
|
||||
| number
|
||||
| object = ColumnTypeToTSType<T>
|
||||
>(dataType: T) {
|
||||
return customType<{
|
||||
data: InferredType | undefined
|
||||
driverData: InferredType | null
|
||||
config: T extends 'stripeId'
|
||||
? {
|
||||
length: number
|
||||
}
|
||||
: never
|
||||
}>({
|
||||
dataType() {
|
||||
if (dataType === 'stripeId') {
|
||||
return 'varchar({ length: 255 })'
|
||||
}
|
||||
|
||||
if (dataType === 'cuid') {
|
||||
return 'varchar({ length: 24 })'
|
||||
}
|
||||
|
||||
if (dataType === 'projectId') {
|
||||
return 'varchar({ length: 130 })'
|
||||
}
|
||||
|
||||
if (dataType === 'deploymentId') {
|
||||
return 'varchar({ length: 160 })'
|
||||
}
|
||||
|
||||
if (dataType === 'timestamp') {
|
||||
return 'timestamp({ mode: "string" })'
|
||||
}
|
||||
|
||||
return dataType
|
||||
},
|
||||
fromDriver: (v) => v ?? undefined,
|
||||
toDriver: (v) => v ?? null
|
||||
})
|
||||
}
|
||||
|
||||
export const optionalText = optional('text')
|
||||
export const optionalTimestamp = optional('timestamp')
|
||||
export const optionalBoolean = optional('boolean')
|
||||
export const optionalVarchar = optional('varchar')
|
||||
export const optionalCuid = optional('cuid')
|
||||
export const optionalStripeId = optional('stripeId')
|
||||
export const optionalProjectId = optional('projectId')
|
||||
export const optionalDeploymentId = optional('deploymentId')
|
||||
|
||||
// ---
|
||||
|
||||
|
||||
type MaybePromise<T> = Promise<T> | T;
|
||||
type RequestTypes = {
|
||||
body?: ZodRequestBody;
|
||||
params?: ZodType;
|
||||
query?: ZodType;
|
||||
cookies?: ZodType;
|
||||
headers?: ZodType | ZodType[];
|
||||
};
|
||||
type InputTypeBase<R extends RouteConfig, Part extends string, Type extends keyof ValidationTargets> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends ZodType ? {
|
||||
in: {
|
||||
[K in Type]: HasUndefined<ValidationTargets[K]> extends true ? {
|
||||
[K2 in keyof z.input<RequestPart<R, Part>>]?: z.input<RequestPart<R, Part>>[K2];
|
||||
} : {
|
||||
[K2 in keyof z.input<RequestPart<R, Part>>]: z.input<RequestPart<R, Part>>[K2];
|
||||
};
|
||||
};
|
||||
out: {
|
||||
[K in Type]: z.output<RequestPart<R, Part>>;
|
||||
};
|
||||
} : {} : {};
|
||||
|
||||
type InputTypeParam<R extends RouteConfig> = InputTypeBase<R, 'params', 'param'>;
|
||||
type InputTypeQuery<R extends RouteConfig> = InputTypeBase<R, 'query', 'query'>;
|
||||
type InputTypeHeader<R extends RouteConfig> = InputTypeBase<R, 'headers', 'header'>;
|
||||
type InputTypeCookie<R extends RouteConfig> = InputTypeBase<R, 'cookies', 'cookie'>;
|
||||
|
||||
type o: <R extends RouteConfig, I extends Input = InputTypeParam<R> & InputTypeQuery<R> & InputTypeHeader<R> & InputTypeCookie<R> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>({ middleware: routeMiddleware, hide, ...route }: R, handler: Handler<R["middleware"] extends MiddlewareHandler[] | MiddlewareHandler ? RouteMiddlewareParam<R>["env"] & E : E, P, I, R extends {
|
||||
responses: {
|
||||
[statusCode: number]: {
|
||||
content: {
|
||||
[mediaType: string]: ZodMediaTypeObject;
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? MaybePromise<RouteConfigToTypedResponse<R>> : MaybePromise<RouteConfigToTypedResponse<R>> | MaybePromise<Response>>) => OpenAPIHono<E, S & ToSchema<R["method"], MergePath<BasePath, P>, I, RouteConfigToTypedResponse<R>>, BasePath>;
|
||||
|
||||
|
||||
// ---
|
||||
|
||||
import type { SetOptional, SetRequired, Simplify } from 'type-fest'
|
||||
import type { AnyZodObject } from 'zod'
|
||||
import { createRoute, type RouteConfig } from '@hono/zod-openapi'
|
||||
|
||||
export type CreateOpenAPIHonoRouteOpts<
|
||||
TAuthenticated extends boolean = true,
|
||||
TMethod extends RouteConfig['method'] = RouteConfig['method']
|
||||
> = Simplify<
|
||||
{
|
||||
authenticated: TAuthenticated
|
||||
paramsSchema?: AnyZodObject
|
||||
bodySchema?: AnyZodObject
|
||||
responseSchema: AnyZodObject
|
||||
method: TMethod
|
||||
} & SetRequired<
|
||||
Omit<
|
||||
SetOptional<Parameters<typeof createRoute>[0], 'responses'>,
|
||||
'request' | 'responses' | 'security'
|
||||
>,
|
||||
'path' | 'operationId' | 'tags' | 'description'
|
||||
>
|
||||
>
|
||||
|
||||
export function createOpenAPIHonoRoute<TAuthenticated extends boolean>(
|
||||
opts: CreateOpenAPIHonoRouteOpts<TAuthenticated>
|
||||
) {
|
||||
const {
|
||||
authenticated,
|
||||
paramsSchema,
|
||||
bodySchema,
|
||||
responseSchema,
|
||||
...createRouteOpts
|
||||
} = opts
|
||||
|
||||
return createRoute({
|
||||
...createRouteOpts,
|
||||
security: authenticated
|
||||
? [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
]
|
||||
: [],
|
||||
request: {
|
||||
params: paramsSchema!,
|
||||
body: bodySchema
|
||||
? {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: bodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: responseSchema.shape.description,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: responseSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// ...openppiErrorResponses
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { relations } from '@fisch0920/drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
|
@ -6,11 +7,12 @@ import {
|
|||
uniqueIndex
|
||||
} from '@fisch0920/drizzle-orm/pg-core'
|
||||
|
||||
import { accounts } from './account'
|
||||
import {
|
||||
authTimestamps,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
stripeId,
|
||||
timestamps,
|
||||
username,
|
||||
// username,
|
||||
userPrimaryId,
|
||||
|
@ -23,17 +25,16 @@ export const users = pgTable(
|
|||
'users',
|
||||
{
|
||||
...userPrimaryId,
|
||||
...authTimestamps,
|
||||
...timestamps,
|
||||
|
||||
name: text().notNull(),
|
||||
username: username().unique().notNull(),
|
||||
role: userRoleEnum().default('user').notNull(),
|
||||
|
||||
name: text(),
|
||||
email: text().notNull().unique(),
|
||||
emailVerified: boolean().default(false).notNull(),
|
||||
image: text(),
|
||||
|
||||
// TODO: re-add username
|
||||
username: username().unique(),
|
||||
role: userRoleEnum().default('user').notNull(),
|
||||
|
||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||
|
||||
stripeCustomerId: stripeId()
|
||||
|
@ -47,6 +48,10 @@ export const users = pgTable(
|
|||
]
|
||||
)
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
accounts: many(accounts)
|
||||
}))
|
||||
|
||||
export const userSelectSchema = createSelectSchema(users)
|
||||
.strip()
|
||||
.openapi('User')
|
||||
|
|
|
@ -78,4 +78,5 @@ export type RawConsumerUpdate = Partial<
|
|||
export type LogEntry = z.infer<typeof schema.logEntrySelectSchema>
|
||||
export type RawLogEntry = InferSelectModel<typeof schema.logEntries>
|
||||
|
||||
export type RawSession = InferSelectModel<typeof schema.sessions>
|
||||
export type Account = z.infer<typeof schema.accountSelectSchema>
|
||||
export type RawAccount = InferSelectModel<typeof schema.accounts>
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
import { validators } from '@agentic/platform-validators'
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||
import { username } from 'better-auth/plugins'
|
||||
|
||||
import { createIdForModel, db, type ModelType } from '@/db'
|
||||
|
||||
import { env } from './env'
|
||||
|
||||
export const auth = betterAuth({
|
||||
appName: 'Agentic',
|
||||
basePath: '/v1/auth',
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg'
|
||||
}),
|
||||
trustedOrigins: [
|
||||
// Used for an oauth redirect when authenticating via the CLI
|
||||
'http://localhost:6013'
|
||||
],
|
||||
emailAndPassword: {
|
||||
enabled: true
|
||||
},
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: env.GITHUB_CLIENT_ID,
|
||||
clientSecret: env.GITHUB_CLIENT_SECRET
|
||||
}
|
||||
},
|
||||
user: {
|
||||
modelName: 'users',
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
defaultValue: 'user',
|
||||
input: false // don't allow user to set role
|
||||
},
|
||||
// username: {
|
||||
// type: 'string',
|
||||
// required: false
|
||||
// },
|
||||
stripeCustomerId: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
},
|
||||
session: {
|
||||
modelName: 'sessions',
|
||||
cookieCache: {
|
||||
enabled: true,
|
||||
maxAge: 10 * 60 // 10 minutes in seconds
|
||||
}
|
||||
},
|
||||
account: {
|
||||
modelName: 'accounts',
|
||||
accountLinking: {
|
||||
enabled: true,
|
||||
trustedProviders: ['github']
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
modelName: 'verifications'
|
||||
},
|
||||
advanced: {
|
||||
database: {
|
||||
generateId: ({ model }) => createIdForModel(model as ModelType)
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
username({
|
||||
usernameValidator: validators.username
|
||||
})
|
||||
]
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
import { createClient as createAuthClient } from '@openauthjs/openauth/client'
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
issuer: 'http://localhost:3000',
|
||||
clientID: 'agentic-internal-api-client'
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
import { createSubjects } from '@openauthjs/openauth/subject'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { userIdSchema } from '@/db'
|
||||
|
||||
export const subjects = createSubjects({
|
||||
user: z.object({
|
||||
id: userIdSchema
|
||||
})
|
||||
})
|
|
@ -0,0 +1,144 @@
|
|||
import type { SetRequired, Simplify } from 'type-fest'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
|
||||
import { and, db, eq, type RawAccount, type RawUser, schema } from '@/db'
|
||||
|
||||
import { getUniqueNamespace } from '../ensure-unique-namespace'
|
||||
|
||||
/**
|
||||
* After a user completes an authentication flow, we'll have partial account info
|
||||
* and partial suer info. This function takes these partial values and maps them
|
||||
* to a valid database Account and User.
|
||||
*
|
||||
* This will result in the Account being upserted, and may result in a new User
|
||||
* being created.
|
||||
*/
|
||||
export async function upsertOrLinkUserAccount({
|
||||
partialAccount,
|
||||
partialUser
|
||||
}: {
|
||||
partialAccount: Simplify<
|
||||
SetRequired<
|
||||
Partial<
|
||||
Pick<
|
||||
RawAccount,
|
||||
| 'provider'
|
||||
| 'accountId'
|
||||
| 'accountUsername'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
| 'accessTokenExpiresAt'
|
||||
| 'refreshTokenExpiresAt'
|
||||
| 'scope'
|
||||
>
|
||||
>,
|
||||
'provider' | 'accountId'
|
||||
>
|
||||
>
|
||||
partialUser: Simplify<
|
||||
SetRequired<
|
||||
Partial<
|
||||
Pick<RawUser, 'email' | 'name' | 'username' | 'image' | 'emailVerified'>
|
||||
>,
|
||||
'email'
|
||||
>
|
||||
>
|
||||
}): Promise<RawUser> {
|
||||
const { provider, accountId } = partialAccount
|
||||
|
||||
const [existingAccount, existingUser] = await Promise.all([
|
||||
db.query.accounts.findFirst({
|
||||
where: and(
|
||||
eq(schema.accounts.provider, provider),
|
||||
eq(schema.accounts.accountId, accountId)
|
||||
),
|
||||
with: {
|
||||
user: true
|
||||
}
|
||||
}),
|
||||
|
||||
db.query.users.findFirst({
|
||||
where: eq(schema.users.email, partialUser.email)
|
||||
})
|
||||
])
|
||||
|
||||
if (existingAccount && existingUser) {
|
||||
// Happy path case: the user is just logging in with an existing account
|
||||
// that's already linked to a user.
|
||||
assert(
|
||||
existingAccount.userId === existingUser.id,
|
||||
`Error authenticating with ${provider}: Account id "${existingAccount.id}" user id "${existingAccount.userId}" does not match expected user id "${existingUser.id}"`
|
||||
)
|
||||
|
||||
// Udate the account with the up-to-date provider data, including any OAuth
|
||||
// tokens.
|
||||
await db
|
||||
.update(schema.accounts)
|
||||
.set(partialAccount)
|
||||
.where(eq(schema.accounts.id, existingAccount.id))
|
||||
|
||||
return existingUser
|
||||
} else if (existingUser && !existingAccount) {
|
||||
// Linking a new account to an existing user
|
||||
await db.insert(schema.accounts).values({
|
||||
...partialAccount,
|
||||
userId: existingUser.id
|
||||
})
|
||||
|
||||
// TODO: Same caveat as below: if the existing user has a different email than
|
||||
// the one in the account we're linking, we should throw an error unless it's
|
||||
// a "trusted" provider.
|
||||
if (provider === 'password' && existingUser.email !== partialUser.email) {
|
||||
await db
|
||||
.update(schema.users)
|
||||
.set(partialUser)
|
||||
.where(eq(schema.users.id, existingUser.id))
|
||||
}
|
||||
|
||||
return existingUser
|
||||
} else if (existingAccount && !existingUser) {
|
||||
assert(
|
||||
existingAccount.user,
|
||||
404,
|
||||
`Error authenticating with ${provider}: Account id "${existingAccount.id}" is linked to a user with a different email address than their ${provider} account, but the linked account user id "${existingAccount.userId}" is not found.`
|
||||
)
|
||||
|
||||
// Existing account is linked to a user with a different email address than
|
||||
// this provider account. This should be fine since it's pretty common for
|
||||
// users to have multiple email addresses, but we may want to limit the
|
||||
// ability to automatically link accounts like this in the future to only
|
||||
// certain, trusted providers like `better-auth` does.
|
||||
return existingAccount.user
|
||||
} else {
|
||||
const username = await getUniqueNamespace(
|
||||
partialUser.username || partialUser.email.split('@')[0]!.toLowerCase(),
|
||||
{ label: 'Username' }
|
||||
)
|
||||
|
||||
// This is a user's first time signing up with the platform, so create both
|
||||
// a new user and linked account.
|
||||
return db.transaction(async (tx) => {
|
||||
// Create a new user
|
||||
const [user] = await tx
|
||||
.insert(schema.users)
|
||||
.values({
|
||||
...partialUser,
|
||||
username
|
||||
})
|
||||
.returning()
|
||||
assert(
|
||||
user,
|
||||
500,
|
||||
`Error creating new user during ${provider} authentication`
|
||||
)
|
||||
|
||||
// Create a new account linked to the new user
|
||||
await tx.insert(schema.accounts).values({
|
||||
...partialAccount,
|
||||
userId: user.id
|
||||
})
|
||||
|
||||
return user
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import type Stripe from 'stripe'
|
|||
import { assert } from '@agentic/platform-core'
|
||||
|
||||
import { db, eq, type RawConsumer, type RawProject, schema } from '@/db'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { stripe } from '@/lib/external/stripe'
|
||||
|
||||
// TODO: Update this for the new / updated Stripe Connect API
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { assert } from '@agentic/platform-core'
|
|||
import type { AuthenticatedContext } from '@/lib/types'
|
||||
import { db, eq, type RawUser, schema } from '@/db'
|
||||
import { ensureAuthUser } from '@/lib/ensure-auth-user'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { stripe } from '@/lib/external/stripe'
|
||||
|
||||
export async function upsertStripeCustomer(ctx: AuthenticatedContext): Promise<{
|
||||
user: RawUser
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
type RawProject,
|
||||
schema
|
||||
} from '@/db'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { stripe } from '@/lib/external/stripe'
|
||||
|
||||
/**
|
||||
* Upserts all the Stripe resources corresponding to a Deployment's pricing
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
type RawUser,
|
||||
schema
|
||||
} from '@/db'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { stripe } from '@/lib/external/stripe'
|
||||
|
||||
import { setConsumerStripeSubscriptionStatus } from '../consumers/utils'
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { assert, sha256 } from '@agentic/platform-core'
|
||||
|
||||
import { db, eq, schema } from '@/db'
|
||||
|
||||
export async function ensureUniqueNamespace(
|
||||
namespace: string,
|
||||
{ label = 'Namespace' }: { label?: string } = {}
|
||||
) {
|
||||
namespace = namespace.toLocaleLowerCase()
|
||||
|
||||
const [existingTeam, existingUser] = await Promise.all([
|
||||
db.query.teams.findFirst({
|
||||
where: eq(schema.teams.slug, namespace)
|
||||
}),
|
||||
|
||||
db.query.users.findFirst({
|
||||
where: eq(schema.users.username, namespace)
|
||||
})
|
||||
])
|
||||
|
||||
assert(
|
||||
!existingUser && !existingTeam,
|
||||
409,
|
||||
`${label} "${namespace}" is not available`
|
||||
)
|
||||
}
|
||||
|
||||
export async function getUniqueNamespace(
|
||||
namespace?: string,
|
||||
{ label = 'Namespace' }: { label?: string } = {}
|
||||
) {
|
||||
namespace ??= `${label}_${sha256().slice(0, 24)}`
|
||||
namespace = namespace
|
||||
.replaceAll(/[^a-zA-Z0-9_-]/g, '')
|
||||
.toLowerCase()
|
||||
.slice(0, schema.namespaceMaxLength - 1)
|
||||
|
||||
let currentNamespace = namespace
|
||||
let attempts = 0
|
||||
|
||||
do {
|
||||
try {
|
||||
await ensureUniqueNamespace(namespace, { label })
|
||||
|
||||
return currentNamespace
|
||||
} catch (err) {
|
||||
if (++attempts > 10) {
|
||||
throw err
|
||||
}
|
||||
|
||||
const suffix = sha256().slice(0, 8)
|
||||
currentNamespace = `${namespace.slice(0, schema.namespaceMaxLength - 1 - suffix.length)}${suffix}`
|
||||
}
|
||||
} while (true)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { assert } from '@agentic/platform-core'
|
||||
|
||||
import { db, eq, schema } from '@/db'
|
||||
|
||||
export async function ensureUniqueTeamSlug(slug: string) {
|
||||
slug = slug.toLocaleLowerCase()
|
||||
|
||||
const [existingTeam, existingUser] = await Promise.all([
|
||||
db.query.teams.findFirst({
|
||||
where: eq(schema.teams.slug, slug)
|
||||
}),
|
||||
|
||||
db.query.users.findFirst({
|
||||
where: eq(schema.users.username, slug)
|
||||
})
|
||||
])
|
||||
|
||||
assert(
|
||||
!existingUser && !existingTeam,
|
||||
409,
|
||||
`Team slug "${slug}" is not available`
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Octokit } from 'octokit'
|
||||
|
||||
export function getGitHubClient({
|
||||
accessToken
|
||||
}: {
|
||||
accessToken: string
|
||||
}): Octokit {
|
||||
return new Octokit({ auth: accessToken })
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Stripe from 'stripe'
|
||||
|
||||
import { env } from './env'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2025-04-30.basil'
|
|
@ -1,38 +1,48 @@
|
|||
import { assert } from '@agentic/platform-core'
|
||||
// import { auth } from '@/lib/auth'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
|
||||
// import * as jwt from 'hono/jwt'
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { authClient } from '@/lib/auth/client'
|
||||
import { subjects } from '@/lib/auth/subjects'
|
||||
|
||||
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
||||
async function authenticateMiddleware(ctx, next) {
|
||||
const session = await auth.api.getSession({
|
||||
// TODO: investigate this type issue
|
||||
headers: ctx.req.raw.headers as any
|
||||
})
|
||||
assert(session, 401, 'Unauthorized')
|
||||
assert(session.user?.id, 401, 'Unauthorized')
|
||||
assert(session.session, 401, 'Unauthorized')
|
||||
// TODO
|
||||
// const session = await auth.api.getSession({
|
||||
// // TODO: investigate this type issue
|
||||
// headers: ctx.req.raw.headers as any
|
||||
// })
|
||||
// assert(session, 401, 'Unauthorized')
|
||||
// assert(session.user?.id, 401, 'Unauthorized')
|
||||
// assert(session.session, 401, 'Unauthorized')
|
||||
|
||||
ctx.set('userId', session.user.id)
|
||||
ctx.set('user', session.user as any) // TODO: resolve AuthUser and RawUser types
|
||||
ctx.set('session', session.session)
|
||||
// ctx.set('userId', session.user.id)
|
||||
// ctx.set('user', session.user as any) // TODO: resolve AuthUser and RawUser types
|
||||
// ctx.set('session', session.session)
|
||||
|
||||
await next()
|
||||
// await next()
|
||||
|
||||
// const credentials = ctx.req.raw.headers.get('Authorization')
|
||||
// assert(credentials, 401, 'Unauthorized')
|
||||
const credentials = ctx.req.raw.headers.get('Authorization')
|
||||
assert(credentials, 401, 'Unauthorized')
|
||||
|
||||
// const parts = credentials.split(/\s+/)
|
||||
// assert(
|
||||
// parts.length === 1 ||
|
||||
// (parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
|
||||
// 401,
|
||||
// 'Unauthorized'
|
||||
// )
|
||||
// const token = parts.at(-1)
|
||||
// assert(token, 401, 'Unauthorized')
|
||||
const parts = credentials.split(/\s+/)
|
||||
assert(
|
||||
parts.length === 1 ||
|
||||
(parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
|
||||
401,
|
||||
'Unauthorized'
|
||||
)
|
||||
const token = parts.at(-1)
|
||||
assert(token, 401, 'Unauthorized')
|
||||
|
||||
const verified = await authClient.verify(subjects, token)
|
||||
assert(!verified.err, 401, 'Unauthorized')
|
||||
|
||||
const userId = verified.subject.properties.id
|
||||
assert(userId, 401, 'Unauthorized')
|
||||
ctx.set('userId', userId)
|
||||
|
||||
// const payload = await jwt.verify(token, env.JWT_SECRET)
|
||||
// assert(payload, 401, 'Unauthorized')
|
||||
|
@ -50,6 +60,6 @@ export const authenticate = createMiddleware<AuthenticatedEnv>(
|
|||
// assert(user, 401, 'Unauthorized')
|
||||
// ctx.set('user', user as any)
|
||||
|
||||
// await next()
|
||||
await next()
|
||||
}
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@ import type { Context } from 'hono'
|
|||
|
||||
import type { RawTeamMember, RawUser } from '@/db'
|
||||
|
||||
import type { auth } from './auth'
|
||||
import type { Env } from './env'
|
||||
import type { Logger } from './logger'
|
||||
|
||||
|
@ -11,9 +10,6 @@ export type { OpenAPI3 as LooseOpenAPI3Spec } from 'openapi-typescript'
|
|||
export type Environment = Env['NODE_ENV']
|
||||
export type Service = 'api'
|
||||
|
||||
// export type AuthUser = typeof auth.$Infer.Session.user
|
||||
export type AuthSession = typeof auth.$Infer.Session.session
|
||||
|
||||
export type DefaultEnvVariables = {
|
||||
requestId: string
|
||||
logger: Logger
|
||||
|
@ -22,7 +18,6 @@ export type DefaultEnvVariables = {
|
|||
export type AuthenticatedEnvVariables = DefaultEnvVariables & {
|
||||
userId: string
|
||||
user?: RawUser
|
||||
session?: AuthSession
|
||||
teamMember?: RawTeamMember
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import '@/lib/sentry'
|
||||
import '@/lib/external/sentry'
|
||||
|
||||
import { serve } from '@hono/node-server'
|
||||
import { sentry } from '@hono/sentry'
|
||||
|
@ -10,6 +10,7 @@ import { apiV1 } from '@/api-v1'
|
|||
import { env } from '@/lib/env'
|
||||
import * as middleware from '@/lib/middleware'
|
||||
|
||||
import { authRouter } from './auth'
|
||||
import { initExitHooks } from './lib/exit-hooks'
|
||||
|
||||
export const app = new OpenAPIHono()
|
||||
|
@ -31,6 +32,8 @@ app.use(middleware.accessLogger)
|
|||
app.use(middleware.responseTime)
|
||||
app.use(middleware.errorHandler)
|
||||
|
||||
app.route('', authRouter)
|
||||
|
||||
app.route('/v1', apiV1)
|
||||
|
||||
app.doc31('/docs', {
|
||||
|
|
|
@ -24,9 +24,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@agentic/platform-core": "workspace:*",
|
||||
"better-auth": "^1.2.8",
|
||||
"@openauthjs/openauth": "^0.4.3",
|
||||
"ky": "catalog:",
|
||||
"type-fest": "catalog:"
|
||||
"type-fest": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"openapi-typescript": "^7.8.0"
|
||||
|
|
|
@ -1,61 +1,153 @@
|
|||
import type { Simplify } from 'type-fest'
|
||||
import { assert, getEnv, sanitizeSearchParams } from '@agentic/platform-core'
|
||||
import { createAuthClient } from 'better-auth/client'
|
||||
import { username } from 'better-auth/plugins'
|
||||
import { assert, sanitizeSearchParams } from '@agentic/platform-core'
|
||||
import {
|
||||
type Client as AuthClient,
|
||||
createClient as createAuthClient
|
||||
} from '@openauthjs/openauth/client'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
|
||||
import type { operations } from './openapi'
|
||||
import type { AuthSession } from './types'
|
||||
import type {
|
||||
AuthorizeResult,
|
||||
AuthTokens,
|
||||
AuthUser,
|
||||
OnUpdateAuthSessionFunction
|
||||
} from './types'
|
||||
import { subjects } from './subjects'
|
||||
|
||||
export class AgenticApiClient {
|
||||
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
|
||||
|
||||
public readonly apiBaseUrl: string
|
||||
public readonly authClient: ReturnType<typeof createAuthClient>
|
||||
public ky: KyInstance
|
||||
public readonly ky: KyInstance
|
||||
public readonly onUpdateAuth?: OnUpdateAuthSessionFunction
|
||||
|
||||
protected _authTokens?: Readonly<AuthTokens>
|
||||
protected _authClient: AuthClient
|
||||
|
||||
constructor({
|
||||
apiCookie = getEnv('AGENTIC_API_COOKIE'),
|
||||
apiBaseUrl = AgenticApiClient.DEFAULT_API_BASE_URL,
|
||||
ky = defaultKy
|
||||
ky = defaultKy,
|
||||
onUpdateAuth
|
||||
}: {
|
||||
apiCookie?: string
|
||||
apiBaseUrl?: string
|
||||
ky?: KyInstance
|
||||
onUpdateAuth?: OnUpdateAuthSessionFunction
|
||||
}) {
|
||||
assert(apiBaseUrl, 'AgenticApiClient missing required "apiBaseUrl"')
|
||||
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
this.onUpdateAuth = onUpdateAuth
|
||||
|
||||
this._authClient = createAuthClient({
|
||||
issuer: apiBaseUrl,
|
||||
clientID: 'agentic-api-client'
|
||||
})
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: apiBaseUrl,
|
||||
// headers: { Authorization: `Bearer ${apiKey}` }
|
||||
headers: { cookie: apiCookie }
|
||||
})
|
||||
|
||||
this.authClient = createAuthClient({
|
||||
baseURL: `${apiBaseUrl}/v1/auth`,
|
||||
plugins: [username()]
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
async (request) => {
|
||||
// Always verify freshness of auth tokens before making a request
|
||||
await this.verifyAuthAndRefreshIfNecessary()
|
||||
assert(this._authTokens, 'Not authenticated')
|
||||
request.headers.set(
|
||||
'Authorization',
|
||||
`Bearer ${this._authTokens.access}`
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getAuthSession(cookie?: string): Promise<AuthSession> {
|
||||
return this.ky
|
||||
.get('v1/auth/get-session', cookie ? { headers: { cookie } } : {})
|
||||
.json<AuthSession>()
|
||||
get isAuthenticated(): boolean {
|
||||
return !!this._authTokens
|
||||
}
|
||||
|
||||
async setAuthSession(cookie: string): Promise<AuthSession> {
|
||||
this.ky = this.ky.extend({
|
||||
headers: { cookie }
|
||||
})
|
||||
|
||||
return this.getAuthSession()
|
||||
get authTokens(): Readonly<AuthTokens> | undefined {
|
||||
return this._authTokens
|
||||
}
|
||||
|
||||
async clearAuthSession(): Promise<void> {
|
||||
this.ky = this.ky.extend({
|
||||
headers: {}
|
||||
async setRefreshAuthToken(refreshToken: string): Promise<void> {
|
||||
const result = await this._authClient.refresh(refreshToken)
|
||||
if (result.err) {
|
||||
throw result.err
|
||||
}
|
||||
|
||||
this._authTokens = result.tokens
|
||||
}
|
||||
|
||||
async verifyAuthAndRefreshIfNecessary(): Promise<AuthUser> {
|
||||
if (!this._authTokens) {
|
||||
throw new Error('This method requires authentication.')
|
||||
}
|
||||
|
||||
const verified = await this._authClient.verify(
|
||||
subjects,
|
||||
this._authTokens.access,
|
||||
{
|
||||
refresh: this._authTokens.refresh
|
||||
}
|
||||
)
|
||||
|
||||
if (verified.err) {
|
||||
throw verified.err
|
||||
}
|
||||
|
||||
if (verified.tokens) {
|
||||
this._authTokens = verified.tokens
|
||||
}
|
||||
|
||||
this.onUpdateAuth?.({
|
||||
session: this._authTokens,
|
||||
user: verified.subject.properties
|
||||
})
|
||||
|
||||
return verified.subject.properties
|
||||
}
|
||||
|
||||
async exchangeAuthCode({
|
||||
code,
|
||||
redirectUri,
|
||||
verifier
|
||||
}: {
|
||||
code: string
|
||||
redirectUri: string
|
||||
verifier?: string
|
||||
}): Promise<AuthUser> {
|
||||
const result = await this._authClient.exchange(code, redirectUri, verifier)
|
||||
|
||||
if (result.err) {
|
||||
throw result.err
|
||||
}
|
||||
|
||||
this._authTokens = result.tokens
|
||||
return this.verifyAuthAndRefreshIfNecessary()
|
||||
}
|
||||
|
||||
async initAuthFlow({
|
||||
redirectUri,
|
||||
provider
|
||||
}: {
|
||||
redirectUri: string
|
||||
provider: 'github' | 'password'
|
||||
}): Promise<AuthorizeResult> {
|
||||
return this._authClient.authorize(redirectUri, 'code', {
|
||||
provider
|
||||
})
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
this._authTokens = undefined
|
||||
this.onUpdateAuth?.()
|
||||
}
|
||||
|
||||
async getMe(): Promise<OperationResponse<'getUser'>> {
|
||||
const user = await this.verifyAuthAndRefreshIfNecessary()
|
||||
|
||||
return this.ky.get(`v1/users/${user.id}`).json()
|
||||
}
|
||||
|
||||
async getUser({
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { createSubjects } from '@openauthjs/openauth/subject'
|
||||
import { z } from 'zod'
|
||||
|
||||
// TODO: share this with the API server
|
||||
export const subjects = createSubjects({
|
||||
user: z.object({
|
||||
id: z.string()
|
||||
})
|
||||
})
|
||||
export type AuthUser = z.infer<typeof subjects.user>
|
|
@ -1,4 +1,7 @@
|
|||
import type { Tokens as AuthTokens } from '@openauthjs/openauth/client'
|
||||
|
||||
import type { components } from './openapi'
|
||||
import type { AuthUser } from './subjects'
|
||||
|
||||
export type Consumer = components['schemas']['Consumer']
|
||||
export type Project = components['schemas']['Project']
|
||||
|
@ -23,30 +26,13 @@ export type PricingPlanName = components['schemas']['name']
|
|||
export type PricingPlanSlug = components['schemas']['slug']
|
||||
export type PricingPlanLabel = components['schemas']['label']
|
||||
|
||||
export type AuthSession = {
|
||||
session: Session
|
||||
export type { AuthUser } from './subjects'
|
||||
export type {
|
||||
AuthorizeResult,
|
||||
Tokens as AuthTokens
|
||||
} from '@openauthjs/openauth/client'
|
||||
|
||||
export type OnUpdateAuthSessionFunction = (update?: {
|
||||
session: AuthTokens
|
||||
user: AuthUser
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
id: string
|
||||
token: string
|
||||
userId: string
|
||||
ipAddress?: string | null
|
||||
userAgent?: string | null
|
||||
expiresAt: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface AuthUser {
|
||||
id: string
|
||||
name: string
|
||||
role: string
|
||||
username?: string
|
||||
email: string
|
||||
emailVerified: boolean
|
||||
image?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
}) => unknown
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import type {
|
||||
AgenticApiClient,
|
||||
AuthSession
|
||||
} from '@agentic/platform-api-client'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
|
||||
import { AuthStore } from './store'
|
||||
|
||||
export async function authWithEmailPassword({
|
||||
client,
|
||||
email,
|
||||
password
|
||||
}: {
|
||||
client: AgenticApiClient
|
||||
email: string
|
||||
password: string
|
||||
}): Promise<AuthSession> {
|
||||
let cookie: string | undefined
|
||||
await client.authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
fetchOptions: {
|
||||
onSuccess: ({ response }) => {
|
||||
cookie = response.headers.get('set-cookie')!
|
||||
}
|
||||
}
|
||||
})
|
||||
assert(cookie, 'Failed to get auth cookie')
|
||||
|
||||
const session = await client.setAuthSession(cookie)
|
||||
assert(session, 'Failed to get auth session')
|
||||
AuthStore.setAuth({ cookie, session })
|
||||
|
||||
return session
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import type {
|
||||
AgenticApiClient,
|
||||
AuthSession
|
||||
} from '@agentic/platform-api-client'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import { serve } from '@hono/node-server'
|
||||
import getPort from 'get-port'
|
||||
import { Hono } from 'hono'
|
||||
import open from 'open'
|
||||
import { oraPromise } from 'ora'
|
||||
|
||||
import { client } from './client'
|
||||
import { AuthStore } from './store'
|
||||
|
||||
export async function authWithGitHub({
|
||||
preferredPort = 6013
|
||||
}: {
|
||||
client: AgenticApiClient
|
||||
preferredPort?: number
|
||||
}): Promise<AuthSession> {
|
||||
const port = await getPort({ port: preferredPort })
|
||||
const app = new Hono()
|
||||
|
||||
if (port !== preferredPort) {
|
||||
throw new Error(
|
||||
`Port ${preferredPort} is required to authenticate with GitHub, but it is already in use.`
|
||||
)
|
||||
}
|
||||
|
||||
let _resolveAuth: any
|
||||
let _rejectAuth: any
|
||||
|
||||
const authP = new Promise<AuthSession>((resolve, reject) => {
|
||||
_resolveAuth = resolve
|
||||
_rejectAuth = reject
|
||||
})
|
||||
|
||||
app.get('/callback/github/success', async (c) => {
|
||||
const cookie = c.req.header().cookie
|
||||
assert(cookie, 'Missing required auth cookie header')
|
||||
|
||||
const session = await client.setAuthSession(cookie)
|
||||
assert(session, 'Failed to get auth session')
|
||||
|
||||
AuthStore.setAuth({ cookie, session })
|
||||
_resolveAuth(session)
|
||||
|
||||
return c.text(
|
||||
'Huzzah! You are now signed in to the Agentic CLI with GitHub.\n\nYou may close this browser tab. 😄'
|
||||
)
|
||||
})
|
||||
|
||||
const server = serve({
|
||||
fetch: app.fetch,
|
||||
port
|
||||
})
|
||||
|
||||
// TODO: clean these up
|
||||
process.on('SIGINT', () => {
|
||||
server.close()
|
||||
process.exit(0)
|
||||
})
|
||||
process.on('SIGTERM', () => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
|
||||
const res = await client.authClient.signIn.social({
|
||||
provider: 'github',
|
||||
// TODO: add error url as well
|
||||
callbackURL: `http://localhost:${port}/callback/github/success`
|
||||
})
|
||||
assert(
|
||||
!res.error,
|
||||
['Error signing in with GitHub', res.error?.code, res.error?.message]
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
)
|
||||
assert(res.data?.url, 'No URL returned from authClient.signIn.social')
|
||||
await open(res.data.url)
|
||||
|
||||
const session = await oraPromise(authP, {
|
||||
text: 'Authenticating with GitHub',
|
||||
successText: 'You are now signed in with GitHub.',
|
||||
failText: 'Failed to authenticate with GitHub.'
|
||||
})
|
||||
server.close()
|
||||
|
||||
return session
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import type { AgenticApiClient } from '@agentic/platform-api-client'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import { serve } from '@hono/node-server'
|
||||
import getPort from 'get-port'
|
||||
import { Hono } from 'hono'
|
||||
import open from 'open'
|
||||
import { oraPromise } from 'ora'
|
||||
|
||||
import type { AuthSession } from './types'
|
||||
import { AuthStore } from './store'
|
||||
|
||||
const providerToLabel = {
|
||||
github: 'GitHub',
|
||||
password: 'email and password'
|
||||
}
|
||||
|
||||
export async function auth({
|
||||
client,
|
||||
provider,
|
||||
preferredPort = 6013
|
||||
}: {
|
||||
client: AgenticApiClient
|
||||
provider: 'github' | 'password'
|
||||
preferredPort?: number
|
||||
}): Promise<AuthSession> {
|
||||
const providerLabel = providerToLabel[provider]!
|
||||
const port = await getPort({ port: preferredPort })
|
||||
const app = new Hono()
|
||||
|
||||
if (port !== preferredPort) {
|
||||
throw new Error(
|
||||
`Port ${preferredPort} is required to sign in with ${providerLabel}, but it is already in use.`
|
||||
)
|
||||
}
|
||||
|
||||
const redirectUri = `http://localhost:${port}/callback/${provider}/success`
|
||||
let _resolveAuth: any
|
||||
let _rejectAuth: any
|
||||
|
||||
const authP = new Promise<AuthSession>((resolve, reject) => {
|
||||
_resolveAuth = resolve
|
||||
_rejectAuth = reject
|
||||
})
|
||||
|
||||
app.get(`/callback/${provider}/success`, async (c) => {
|
||||
// console.log(`/callback/${provider}/success`, c.req.method, c.req.url, {
|
||||
// headers: c.req.header(),
|
||||
// query: c.req.query()
|
||||
// })
|
||||
|
||||
const code = c.req.query('code')
|
||||
assert(code, 'Missing required code query parameter')
|
||||
await client.exchangeAuthCode({
|
||||
code,
|
||||
redirectUri,
|
||||
verifier: authorizeResult.challenge?.verifier
|
||||
})
|
||||
assert(
|
||||
client.authTokens,
|
||||
`Error ${providerLabel} auth: failed to exchange auth code for token`
|
||||
)
|
||||
|
||||
// AuthStore should be updated via the onUpdateAuth callback
|
||||
const session = AuthStore.tryGetAuth()
|
||||
assert(session && session.refreshToken === client.authTokens.refresh)
|
||||
_resolveAuth(session)
|
||||
|
||||
return c.text(
|
||||
`Huzzah! You are now signed in to the Agentic CLI.\n\nYou may close this browser tab. 😄`
|
||||
)
|
||||
})
|
||||
|
||||
const server = serve({
|
||||
fetch: app.fetch,
|
||||
port
|
||||
})
|
||||
|
||||
// TODO: clean these up
|
||||
process.on('SIGINT', () => {
|
||||
server.close()
|
||||
process.exit(0)
|
||||
})
|
||||
process.on('SIGTERM', () => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
|
||||
const authorizeResult = await client.initAuthFlow({
|
||||
provider,
|
||||
redirectUri
|
||||
})
|
||||
assert(authorizeResult.url, `Error signing in with ${providerLabel}`)
|
||||
await open(authorizeResult.url)
|
||||
|
||||
const authSession = await oraPromise(authP, {
|
||||
text: `Signing in with ${providerLabel}`,
|
||||
successText: 'You are now signed in.',
|
||||
failText: 'Failed to sign in.'
|
||||
})
|
||||
server.close()
|
||||
|
||||
return authSession
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { AgenticApiClient } from '@agentic/platform-api-client'
|
||||
|
||||
import { AuthStore } from './store'
|
||||
|
||||
// Create a singleton instance of the API client
|
||||
export const client = new AgenticApiClient({
|
||||
apiCookie: AuthStore.tryGetAuth()?.cookie,
|
||||
apiBaseUrl: 'http://localhost:3000'
|
||||
})
|
|
@ -1,9 +1,7 @@
|
|||
import type { AuthSession } from '@agentic/platform-api-client'
|
||||
import { Command, InvalidArgumentError } from 'commander'
|
||||
import { Command } from 'commander'
|
||||
|
||||
import type { Context } from '../types'
|
||||
import { authWithEmailPassword } from '../auth-with-email-password'
|
||||
import { authWithGitHub } from '../auth-with-github'
|
||||
import { auth } from '../auth'
|
||||
|
||||
export function registerSigninCommand({ client, program, logger }: Context) {
|
||||
const command = new Command('login')
|
||||
|
@ -11,29 +9,21 @@ export function registerSigninCommand({ client, program, logger }: Context) {
|
|||
.description(
|
||||
'Signs in to Agentic. If no credentials are provided, uses GitHub auth.'
|
||||
)
|
||||
// TODO
|
||||
// .option('-u, --username <username>', 'account username')
|
||||
.option('-e, --email <email>', 'account email')
|
||||
.option('-p, --password <password>', 'account password')
|
||||
.option('-e, --email', 'Log in using email and password')
|
||||
.action(async (opts) => {
|
||||
let session: AuthSession | undefined
|
||||
if (opts.email) {
|
||||
if (!opts.password) {
|
||||
throw new InvalidArgumentError(
|
||||
'Password is required when using email'
|
||||
)
|
||||
}
|
||||
|
||||
session = await authWithEmailPassword({
|
||||
await auth({
|
||||
client,
|
||||
email: opts.email,
|
||||
password: opts.password
|
||||
provider: 'password'
|
||||
// email: opts.email,
|
||||
// password: opts.password
|
||||
})
|
||||
} else {
|
||||
session = await authWithGitHub({ client })
|
||||
await auth({ client, provider: 'github' })
|
||||
}
|
||||
|
||||
logger.log(session)
|
||||
const user = await client.getMe()
|
||||
logger.log(user)
|
||||
})
|
||||
|
||||
program.addCommand(command)
|
||||
|
|
|
@ -8,11 +8,11 @@ export function registerSignoutCommand({ client, program, logger }: Context) {
|
|||
.alias('signout')
|
||||
.description('Signs the current user out')
|
||||
.action(async () => {
|
||||
if (!AuthStore.isAuthenticated()) {
|
||||
if (!client.isAuthenticated) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.clearAuthSession()
|
||||
await client.logout()
|
||||
AuthStore.clearAuth()
|
||||
|
||||
logger.log('Signed out')
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { Command } from 'commander'
|
||||
|
||||
import type { Context } from '../types'
|
||||
import { AuthStore } from '../store'
|
||||
|
||||
export function registerWhoAmICommand({ client, program, logger }: Context) {
|
||||
const command = new Command('whoami')
|
||||
.description('Displays info about the current user')
|
||||
.action(async () => {
|
||||
if (!AuthStore.isAuthenticated()) {
|
||||
if (!client.isAuthenticated) {
|
||||
logger.log('Not signed in')
|
||||
return
|
||||
}
|
||||
|
||||
const res = await client.getAuthSession()
|
||||
const res = await client.getMe()
|
||||
logger.log(res)
|
||||
})
|
||||
|
||||
|
|
|
@ -13,10 +13,33 @@ async function main() {
|
|||
restoreCursor()
|
||||
|
||||
const client = new AgenticApiClient({
|
||||
apiCookie: AuthStore.tryGetAuth()?.cookie,
|
||||
apiBaseUrl: process.env.AGENTIC_API_BASE_URL
|
||||
apiBaseUrl: process.env.AGENTIC_API_BASE_URL,
|
||||
onUpdateAuth: (update) => {
|
||||
if (update) {
|
||||
AuthStore.setAuth({
|
||||
refreshToken: update.session.refresh,
|
||||
user: update.user
|
||||
})
|
||||
} else {
|
||||
AuthStore.clearAuth()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Try to initialize the existing auth session if one exists
|
||||
const authSession = AuthStore.tryGetAuth()
|
||||
if (authSession) {
|
||||
try {
|
||||
await client.setRefreshAuthToken(authSession.refreshToken)
|
||||
} catch (err: any) {
|
||||
console.warn(
|
||||
'Existing auth session is invalid; logging out...',
|
||||
err.message
|
||||
)
|
||||
AuthStore.clearAuth()
|
||||
}
|
||||
}
|
||||
|
||||
const program = new Command('agentic')
|
||||
.option('-j, --json', 'Print output in JSON format')
|
||||
.showHelpAfterError()
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import type { AuthSession } from '@agentic/platform-api-client'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import Conf from 'conf'
|
||||
|
||||
export type AuthState = {
|
||||
cookie: string
|
||||
session: AuthSession
|
||||
teamId?: string
|
||||
teamSlug?: string
|
||||
}
|
||||
import type { AuthSession } from './types'
|
||||
|
||||
const keyTeamId = 'teamId'
|
||||
const keyTeamSlug = 'teamSlug'
|
||||
const keyCookie = 'cookie'
|
||||
const keySession = 'session'
|
||||
const keyAuthSession = 'authSession'
|
||||
|
||||
export const AuthStore = {
|
||||
store: new Conf({ projectName: 'agentic' }),
|
||||
store: new Conf<{
|
||||
authSession: AuthSession
|
||||
}>({ projectName: 'agentic' }),
|
||||
|
||||
isAuthenticated() {
|
||||
return this.store.has(keyCookie) && this.store.has(keySession)
|
||||
return this.store.has(keyAuthSession)
|
||||
},
|
||||
|
||||
requireAuth() {
|
||||
|
@ -28,43 +21,34 @@ export const AuthStore = {
|
|||
)
|
||||
},
|
||||
|
||||
tryGetAuth(): AuthState | undefined {
|
||||
tryGetAuth(): AuthSession | undefined {
|
||||
if (!this.isAuthenticated()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
cookie: this.store.get(keyCookie),
|
||||
session: this.store.get(keySession),
|
||||
teamId: this.store.get(keyTeamId),
|
||||
teamSlug: this.store.get(keyTeamSlug)
|
||||
} as AuthState
|
||||
return this.store.get(keyAuthSession)
|
||||
},
|
||||
|
||||
getAuth(): AuthState {
|
||||
getAuth(): AuthSession {
|
||||
this.requireAuth()
|
||||
return this.tryGetAuth()!
|
||||
},
|
||||
|
||||
setAuth({ cookie, session }: { cookie: string; session: AuthSession }) {
|
||||
this.store.set(keyCookie, cookie)
|
||||
this.store.set(keySession, session)
|
||||
setAuth(authSession: AuthSession) {
|
||||
this.store.set(keyAuthSession, authSession)
|
||||
},
|
||||
|
||||
clearAuth() {
|
||||
this.store.delete(keyCookie)
|
||||
this.store.delete(keySession)
|
||||
this.store.delete(keyTeamId)
|
||||
this.store.delete(keyTeamSlug)
|
||||
},
|
||||
|
||||
switchTeam(team?: { id: string; slug: string }) {
|
||||
if (team?.id) {
|
||||
this.store.set(keyTeamId, team.id)
|
||||
this.store.set(keyTeamSlug, team.slug)
|
||||
} else {
|
||||
this.store.delete(keyTeamId)
|
||||
this.store.delete(keyTeamSlug)
|
||||
}
|
||||
this.store.delete(keyAuthSession)
|
||||
}
|
||||
|
||||
// switchTeam(team?: { id: string; slug: string }) {
|
||||
// if (team?.id) {
|
||||
// this.store.set(keyTeamId, team.id)
|
||||
// this.store.set(keyTeamSlug, team.slug)
|
||||
// } else {
|
||||
// this.store.delete(keyTeamId)
|
||||
// this.store.delete(keyTeamSlug)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { AgenticApiClient } from '@agentic/platform-api-client'
|
||||
import type { AgenticApiClient, AuthUser } from '@agentic/platform-api-client'
|
||||
import type { Command } from 'commander'
|
||||
|
||||
export type Context = {
|
||||
|
@ -8,3 +8,8 @@ export type Context = {
|
|||
log: (...args: any[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
export type AuthSession = {
|
||||
refreshToken: string
|
||||
user: AuthUser
|
||||
}
|
||||
|
|
573
pnpm-lock.yaml
573
pnpm-lock.yaml
|
@ -149,6 +149,9 @@ importers:
|
|||
'@hono/zod-openapi':
|
||||
specifier: ^0.19.6
|
||||
version: 0.19.6(hono@4.7.9)(zod@3.24.4)
|
||||
'@openauthjs/openauth':
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3(arctic@2.3.4)(hono@4.7.9)
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
|
@ -158,9 +161,6 @@ importers:
|
|||
'@sentry/node':
|
||||
specifier: ^9.19.0
|
||||
version: 9.19.0
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
eventid:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
|
@ -173,6 +173,9 @@ importers:
|
|||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
octokit:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
p-all:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
|
@ -222,15 +225,18 @@ importers:
|
|||
'@agentic/platform-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
'@openauthjs/openauth':
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3(arctic@2.3.4)(hono@4.7.9)
|
||||
ky:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.1
|
||||
type-fest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.41.0
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.4
|
||||
devDependencies:
|
||||
openapi-typescript:
|
||||
specifier: ^7.8.0
|
||||
|
@ -332,12 +338,6 @@ packages:
|
|||
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@better-auth/utils@0.2.5':
|
||||
resolution: {integrity: sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==}
|
||||
|
||||
'@better-fetch/fetch@1.1.18':
|
||||
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
||||
|
||||
'@commander-js/extra-typings@14.0.0':
|
||||
resolution: {integrity: sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg==}
|
||||
peerDependencies:
|
||||
|
@ -783,9 +783,6 @@ packages:
|
|||
'@fisch0920/drizzle-orm': '>=0.36.0'
|
||||
zod: '>=3.0.0'
|
||||
|
||||
'@hexagon/base64@1.1.28':
|
||||
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||
|
||||
'@hono/node-server@1.14.1':
|
||||
resolution: {integrity: sha512-vmbuM+HPinjWzPe7FFPWMMQMsbKE9gDPhaH0FFdqbGpkT5lp++tcWDTxwBl5EgS5y6JVgIaCdjeHRfQ4XRBRjQ==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
|
@ -856,16 +853,10 @@ packages:
|
|||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@levischuck/tiny-cbor@0.2.11':
|
||||
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
|
||||
|
||||
'@modelcontextprotocol/sdk@1.11.2':
|
||||
resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@noble/ciphers@0.6.0':
|
||||
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
|
||||
|
||||
'@noble/hashes@1.8.0':
|
||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
|
@ -882,6 +873,119 @@ packages:
|
|||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@octokit/app@16.0.1':
|
||||
resolution: {integrity: sha512-kgTeTsWmpUX+s3Fs4EK4w1K+jWCDB6ClxLSWUWTyhlw7+L3jHtuXDR4QtABu2GsmCMdk67xRhruiXotS3ay3Yw==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-app@8.0.1':
|
||||
resolution: {integrity: sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-oauth-app@9.0.1':
|
||||
resolution: {integrity: sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-oauth-device@8.0.1':
|
||||
resolution: {integrity: sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-oauth-user@6.0.0':
|
||||
resolution: {integrity: sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-token@6.0.0':
|
||||
resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/auth-unauthenticated@7.0.1':
|
||||
resolution: {integrity: sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/core@7.0.2':
|
||||
resolution: {integrity: sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/endpoint@11.0.0':
|
||||
resolution: {integrity: sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/graphql@9.0.1':
|
||||
resolution: {integrity: sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/oauth-app@8.0.1':
|
||||
resolution: {integrity: sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/oauth-authorization-url@8.0.0':
|
||||
resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/oauth-methods@6.0.0':
|
||||
resolution: {integrity: sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/openapi-types@25.0.0':
|
||||
resolution: {integrity: sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==}
|
||||
|
||||
'@octokit/openapi-webhooks-types@11.0.0':
|
||||
resolution: {integrity: sha512-ZBzCFj98v3SuRM7oBas6BHZMJRadlnDoeFfvm1olVxZnYeU6Vh97FhPxyS5aLh5pN51GYv2I51l/hVUAVkGBlA==}
|
||||
|
||||
'@octokit/plugin-paginate-graphql@6.0.0':
|
||||
resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=6'
|
||||
|
||||
'@octokit/plugin-paginate-rest@13.0.0':
|
||||
resolution: {integrity: sha512-nPXM3wgil9ONnAINcm8cN+nwso4QhNB13PtnlRFkYFHCUIogcH9DHak/StQYcwkkjuc7pUluLG1AWZNscgvH7Q==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=6'
|
||||
|
||||
'@octokit/plugin-rest-endpoint-methods@15.0.0':
|
||||
resolution: {integrity: sha512-db6UdWvpX7O6tNsdkPk1BttVwTeVdA4n8RDFeXOyjBCPjE2YPufIAlzWh8CyeH8hl/3dSuQXDa+qLmsBlkTY+Q==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=6'
|
||||
|
||||
'@octokit/plugin-retry@8.0.1':
|
||||
resolution: {integrity: sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=7'
|
||||
|
||||
'@octokit/plugin-throttling@11.0.1':
|
||||
resolution: {integrity: sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==}
|
||||
engines: {node: '>= 20'}
|
||||
peerDependencies:
|
||||
'@octokit/core': ^7.0.0
|
||||
|
||||
'@octokit/request-error@7.0.0':
|
||||
resolution: {integrity: sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/request@10.0.2':
|
||||
resolution: {integrity: sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/types@14.0.0':
|
||||
resolution: {integrity: sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==}
|
||||
|
||||
'@octokit/webhooks-methods@6.0.0':
|
||||
resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@octokit/webhooks@14.0.0':
|
||||
resolution: {integrity: sha512-IZV4vg/s1pqIpCs86a0tp5FQ/O94DUaqksMdNrXFSaE037TXsB+fIhr8OVig09oEx3WazVgE6B2U+u7/Fvdlsw==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@openauthjs/openauth@0.4.3':
|
||||
resolution: {integrity: sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw==}
|
||||
peerDependencies:
|
||||
arctic: ^2.2.2
|
||||
hono: ^4.0.0
|
||||
|
||||
'@opentelemetry/api-logs@0.57.2':
|
||||
resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -1070,24 +1174,27 @@ packages:
|
|||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
|
||||
'@oslojs/asn1@1.0.0':
|
||||
resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
|
||||
|
||||
'@oslojs/binary@1.0.0':
|
||||
resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
|
||||
|
||||
'@oslojs/crypto@1.0.1':
|
||||
resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
|
||||
|
||||
'@oslojs/encoding@0.4.1':
|
||||
resolution: {integrity: sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==}
|
||||
|
||||
'@oslojs/encoding@1.1.0':
|
||||
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||
|
||||
'@oslojs/jwt@0.2.0':
|
||||
resolution: {integrity: sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg==}
|
||||
|
||||
'@paralleldrive/cuid2@2.2.2':
|
||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||
|
||||
'@peculiar/asn1-android@2.3.16':
|
||||
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
|
||||
|
||||
'@peculiar/asn1-ecc@2.3.15':
|
||||
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
|
||||
|
||||
'@peculiar/asn1-rsa@2.3.15':
|
||||
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
|
||||
|
||||
'@peculiar/asn1-schema@2.3.15':
|
||||
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
|
||||
|
||||
'@peculiar/asn1-x509@2.3.15':
|
||||
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -1241,20 +1348,19 @@ packages:
|
|||
resolution: {integrity: sha512-A4srR9mEBFdVXwSEKjQ94msUbVkMr8JeFiEj9ouOFORw/Y/ux/WV2bWVD/ZI9wq0TcTNK8L1wBgU8UMS5lIq3A==}
|
||||
engines: {node: '>=14.18'}
|
||||
|
||||
'@simplewebauthn/browser@13.1.0':
|
||||
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
|
||||
|
||||
'@simplewebauthn/server@13.1.1':
|
||||
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@sindresorhus/merge-streams@2.3.0':
|
||||
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@standard-schema/spec@1.0.0-beta.3':
|
||||
resolution: {integrity: sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==}
|
||||
|
||||
'@total-typescript/ts-reset@0.6.1':
|
||||
resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==}
|
||||
|
||||
'@types/aws-lambda@8.10.149':
|
||||
resolution: {integrity: sha512-NXSZIhfJjnXqJgtS7IwutqIF/SOy1Wz5Px4gUY1RWITp3AYTyuJS4xaXr/bIJY1v15XMzrJ5soGnPM+7uigZjA==}
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
|
||||
|
@ -1460,6 +1566,9 @@ packages:
|
|||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
arctic@2.3.4:
|
||||
resolution: {integrity: sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
|
@ -1499,10 +1608,6 @@ packages:
|
|||
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
asn1js@3.0.6:
|
||||
resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -1521,6 +1626,9 @@ packages:
|
|||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
aws4fetch@1.0.20:
|
||||
resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==}
|
||||
|
||||
axe-core@4.10.3:
|
||||
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -1535,11 +1643,8 @@ packages:
|
|||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
better-auth@1.2.8:
|
||||
resolution: {integrity: sha512-y8ry7ZW3/3ZIr82Eo1zUDtMzdoQlFnwNuZ0+b0RxoNZgqmvgTIc/0tCDC7NDJerqSu4UCzer0dvYxBsv3WMIGg==}
|
||||
|
||||
better-call@1.0.9:
|
||||
resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==}
|
||||
before-after-hook@4.0.0:
|
||||
resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
@ -1548,6 +1653,9 @@ packages:
|
|||
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bottleneck@2.19.5:
|
||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
|
||||
|
@ -1816,9 +1924,6 @@ packages:
|
|||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
del-cli@6.0.0:
|
||||
resolution: {integrity: sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==}
|
||||
engines: {node: '>=18'}
|
||||
|
@ -2223,6 +2328,9 @@ packages:
|
|||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
fast-content-type-parse@3.0.0:
|
||||
resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
|
@ -2662,8 +2770,8 @@ packages:
|
|||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
jose@5.10.0:
|
||||
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
|
||||
jose@5.9.6:
|
||||
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
|
@ -2917,10 +3025,6 @@ packages:
|
|||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
nanostores@0.11.4:
|
||||
resolution: {integrity: sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
|
@ -2976,6 +3080,10 @@ packages:
|
|||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
octokit@5.0.2:
|
||||
resolution: {integrity: sha512-WCO9Oip2F+qsrIcNMfLwm1+dL2g70oO++pkmiluisJDMXXwdO4susVaVg1iQZgZNiDtA1qcLXs5662Mdj/vqdw==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -3198,13 +3306,6 @@ packages:
|
|||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
pvtsutils@1.3.6:
|
||||
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
|
||||
|
||||
pvutils@1.1.3:
|
||||
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
qs@6.14.0:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
@ -3314,9 +3415,6 @@ packages:
|
|||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
rou3@0.5.1:
|
||||
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
|
||||
|
||||
router@2.2.0:
|
||||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
@ -3373,9 +3471,6 @@ packages:
|
|||
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -3621,6 +3716,10 @@ packages:
|
|||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
toad-cache@3.7.0:
|
||||
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
@ -3770,9 +3869,6 @@ packages:
|
|||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
uncrypto@0.1.3:
|
||||
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
|
@ -3784,6 +3880,12 @@ packages:
|
|||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
universal-github-app-jwt@2.2.2:
|
||||
resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==}
|
||||
|
||||
universal-user-agent@7.0.3:
|
||||
resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==}
|
||||
|
||||
unpipe@1.0.0:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -4022,13 +4124,6 @@ snapshots:
|
|||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@better-auth/utils@0.2.5':
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
uncrypto: 0.1.3
|
||||
|
||||
'@better-fetch/fetch@1.1.18': {}
|
||||
|
||||
'@commander-js/extra-typings@14.0.0(commander@14.0.0)':
|
||||
dependencies:
|
||||
commander: 14.0.0
|
||||
|
@ -4281,8 +4376,6 @@ snapshots:
|
|||
'@fisch0920/drizzle-orm': 0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.5)
|
||||
zod: 3.24.4
|
||||
|
||||
'@hexagon/base64@1.1.28': {}
|
||||
|
||||
'@hono/node-server@1.14.1(hono@4.7.9)':
|
||||
dependencies:
|
||||
hono: 4.7.9
|
||||
|
@ -4345,8 +4438,6 @@ snapshots:
|
|||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@levischuck/tiny-cbor@0.2.11': {}
|
||||
|
||||
'@modelcontextprotocol/sdk@1.11.2':
|
||||
dependencies:
|
||||
content-type: 1.0.5
|
||||
|
@ -4362,8 +4453,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@noble/ciphers@0.6.0': {}
|
||||
|
||||
'@noble/hashes@1.8.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
|
@ -4378,6 +4467,161 @@ snapshots:
|
|||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.19.1
|
||||
|
||||
'@octokit/app@16.0.1':
|
||||
dependencies:
|
||||
'@octokit/auth-app': 8.0.1
|
||||
'@octokit/auth-unauthenticated': 7.0.1
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/oauth-app': 8.0.1
|
||||
'@octokit/plugin-paginate-rest': 13.0.0(@octokit/core@7.0.2)
|
||||
'@octokit/types': 14.0.0
|
||||
'@octokit/webhooks': 14.0.0
|
||||
|
||||
'@octokit/auth-app@8.0.1':
|
||||
dependencies:
|
||||
'@octokit/auth-oauth-app': 9.0.1
|
||||
'@octokit/auth-oauth-user': 6.0.0
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
toad-cache: 3.7.0
|
||||
universal-github-app-jwt: 2.2.2
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/auth-oauth-app@9.0.1':
|
||||
dependencies:
|
||||
'@octokit/auth-oauth-device': 8.0.1
|
||||
'@octokit/auth-oauth-user': 6.0.0
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/auth-oauth-device@8.0.1':
|
||||
dependencies:
|
||||
'@octokit/oauth-methods': 6.0.0
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/auth-oauth-user@6.0.0':
|
||||
dependencies:
|
||||
'@octokit/auth-oauth-device': 8.0.1
|
||||
'@octokit/oauth-methods': 6.0.0
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/auth-token@6.0.0': {}
|
||||
|
||||
'@octokit/auth-unauthenticated@7.0.1':
|
||||
dependencies:
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
|
||||
'@octokit/core@7.0.2':
|
||||
dependencies:
|
||||
'@octokit/auth-token': 6.0.0
|
||||
'@octokit/graphql': 9.0.1
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
before-after-hook: 4.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/endpoint@11.0.0':
|
||||
dependencies:
|
||||
'@octokit/types': 14.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/graphql@9.0.1':
|
||||
dependencies:
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/oauth-app@8.0.1':
|
||||
dependencies:
|
||||
'@octokit/auth-oauth-app': 9.0.1
|
||||
'@octokit/auth-oauth-user': 6.0.0
|
||||
'@octokit/auth-unauthenticated': 7.0.1
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/oauth-authorization-url': 8.0.0
|
||||
'@octokit/oauth-methods': 6.0.0
|
||||
'@types/aws-lambda': 8.10.149
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/oauth-authorization-url@8.0.0': {}
|
||||
|
||||
'@octokit/oauth-methods@6.0.0':
|
||||
dependencies:
|
||||
'@octokit/oauth-authorization-url': 8.0.0
|
||||
'@octokit/request': 10.0.2
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
|
||||
'@octokit/openapi-types@25.0.0': {}
|
||||
|
||||
'@octokit/openapi-webhooks-types@11.0.0': {}
|
||||
|
||||
'@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.2)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.2
|
||||
|
||||
'@octokit/plugin-paginate-rest@13.0.0(@octokit/core@7.0.2)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
|
||||
'@octokit/plugin-rest-endpoint-methods@15.0.0(@octokit/core@7.0.2)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
|
||||
'@octokit/plugin-retry@8.0.1(@octokit/core@7.0.2)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
bottleneck: 2.19.5
|
||||
|
||||
'@octokit/plugin-throttling@11.0.1(@octokit/core@7.0.2)':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/types': 14.0.0
|
||||
bottleneck: 2.19.5
|
||||
|
||||
'@octokit/request-error@7.0.0':
|
||||
dependencies:
|
||||
'@octokit/types': 14.0.0
|
||||
|
||||
'@octokit/request@10.0.2':
|
||||
dependencies:
|
||||
'@octokit/endpoint': 11.0.0
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
fast-content-type-parse: 3.0.0
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/types@14.0.0':
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 25.0.0
|
||||
|
||||
'@octokit/webhooks-methods@6.0.0': {}
|
||||
|
||||
'@octokit/webhooks@14.0.0':
|
||||
dependencies:
|
||||
'@octokit/openapi-webhooks-types': 11.0.0
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/webhooks-methods': 6.0.0
|
||||
|
||||
'@openauthjs/openauth@0.4.3(arctic@2.3.4)(hono@4.7.9)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0-beta.3
|
||||
arctic: 2.3.4
|
||||
aws4fetch: 1.0.20
|
||||
hono: 4.7.9
|
||||
jose: 5.9.6
|
||||
|
||||
'@opentelemetry/api-logs@0.57.2':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
|
@ -4620,43 +4864,29 @@ snapshots:
|
|||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@oslojs/asn1@1.0.0':
|
||||
dependencies:
|
||||
'@oslojs/binary': 1.0.0
|
||||
|
||||
'@oslojs/binary@1.0.0': {}
|
||||
|
||||
'@oslojs/crypto@1.0.1':
|
||||
dependencies:
|
||||
'@oslojs/asn1': 1.0.0
|
||||
'@oslojs/binary': 1.0.0
|
||||
|
||||
'@oslojs/encoding@0.4.1': {}
|
||||
|
||||
'@oslojs/encoding@1.1.0': {}
|
||||
|
||||
'@oslojs/jwt@0.2.0':
|
||||
dependencies:
|
||||
'@oslojs/encoding': 0.4.1
|
||||
|
||||
'@paralleldrive/cuid2@2.2.2':
|
||||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
|
||||
'@peculiar/asn1-android@2.3.16':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
asn1js: 3.0.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-ecc@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
asn1js: 3.0.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-rsa@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
asn1js: 3.0.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-schema@2.3.15':
|
||||
dependencies:
|
||||
asn1js: 3.0.6
|
||||
pvtsutils: 1.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-x509@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
asn1js: 3.0.6
|
||||
pvtsutils: 1.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
|
@ -4814,22 +5044,14 @@ snapshots:
|
|||
dependencies:
|
||||
'@sentry/types': 8.9.2
|
||||
|
||||
'@simplewebauthn/browser@13.1.0': {}
|
||||
|
||||
'@simplewebauthn/server@13.1.1':
|
||||
dependencies:
|
||||
'@hexagon/base64': 1.1.28
|
||||
'@levischuck/tiny-cbor': 0.2.11
|
||||
'@peculiar/asn1-android': 2.3.16
|
||||
'@peculiar/asn1-ecc': 2.3.15
|
||||
'@peculiar/asn1-rsa': 2.3.15
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
|
||||
'@sindresorhus/merge-streams@2.3.0': {}
|
||||
|
||||
'@standard-schema/spec@1.0.0-beta.3': {}
|
||||
|
||||
'@total-typescript/ts-reset@0.6.1': {}
|
||||
|
||||
'@types/aws-lambda@8.10.149': {}
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 22.15.18
|
||||
|
@ -5066,6 +5288,12 @@ snapshots:
|
|||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
arctic@2.3.4:
|
||||
dependencies:
|
||||
'@oslojs/crypto': 1.0.1
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@oslojs/jwt': 0.2.0
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
@ -5135,12 +5363,6 @@ snapshots:
|
|||
get-intrinsic: 1.3.0
|
||||
is-array-buffer: 3.0.5
|
||||
|
||||
asn1js@3.0.6:
|
||||
dependencies:
|
||||
pvtsutils: 1.3.6
|
||||
pvutils: 1.1.3
|
||||
tslib: 2.8.1
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
ast-types-flow@0.0.8: {}
|
||||
|
@ -5156,6 +5378,8 @@ snapshots:
|
|||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
|
||||
aws4fetch@1.0.20: {}
|
||||
|
||||
axe-core@4.10.3: {}
|
||||
|
||||
axobject-query@4.1.0: {}
|
||||
|
@ -5164,27 +5388,7 @@ snapshots:
|
|||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
better-auth@1.2.8:
|
||||
dependencies:
|
||||
'@better-auth/utils': 0.2.5
|
||||
'@better-fetch/fetch': 1.1.18
|
||||
'@noble/ciphers': 0.6.0
|
||||
'@noble/hashes': 1.8.0
|
||||
'@simplewebauthn/browser': 13.1.0
|
||||
'@simplewebauthn/server': 13.1.1
|
||||
better-call: 1.0.9
|
||||
defu: 6.1.4
|
||||
jose: 5.10.0
|
||||
kysely: 0.28.2
|
||||
nanostores: 0.11.4
|
||||
zod: 3.24.4
|
||||
|
||||
better-call@1.0.9:
|
||||
dependencies:
|
||||
'@better-fetch/fetch': 1.1.18
|
||||
rou3: 0.5.1
|
||||
set-cookie-parser: 2.7.1
|
||||
uncrypto: 0.1.3
|
||||
before-after-hook@4.0.0: {}
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
|
@ -5206,6 +5410,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bottleneck@2.19.5: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
@ -5449,8 +5655,6 @@ snapshots:
|
|||
has-property-descriptors: 1.0.2
|
||||
object-keys: 1.1.1
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
del-cli@6.0.0:
|
||||
dependencies:
|
||||
del: 8.0.0
|
||||
|
@ -5968,6 +6172,8 @@ snapshots:
|
|||
iconv-lite: 0.4.24
|
||||
tmp: 0.0.33
|
||||
|
||||
fast-content-type-parse@3.0.0: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
|
@ -6410,7 +6616,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jose@5.10.0: {}
|
||||
jose@5.9.6: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
|
@ -6479,7 +6685,8 @@ snapshots:
|
|||
|
||||
ky@1.8.1: {}
|
||||
|
||||
kysely@0.28.2: {}
|
||||
kysely@0.28.2:
|
||||
optional: true
|
||||
|
||||
language-subtag-registry@0.3.23: {}
|
||||
|
||||
|
@ -6639,8 +6846,6 @@ snapshots:
|
|||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
nanostores@0.11.4: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
@ -6708,6 +6913,20 @@ snapshots:
|
|||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
octokit@5.0.2:
|
||||
dependencies:
|
||||
'@octokit/app': 16.0.1
|
||||
'@octokit/core': 7.0.2
|
||||
'@octokit/oauth-app': 8.0.1
|
||||
'@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.2)
|
||||
'@octokit/plugin-paginate-rest': 13.0.0(@octokit/core@7.0.2)
|
||||
'@octokit/plugin-rest-endpoint-methods': 15.0.0(@octokit/core@7.0.2)
|
||||
'@octokit/plugin-retry': 8.0.1(@octokit/core@7.0.2)
|
||||
'@octokit/plugin-throttling': 11.0.1(@octokit/core@7.0.2)
|
||||
'@octokit/request-error': 7.0.0
|
||||
'@octokit/types': 14.0.0
|
||||
'@octokit/webhooks': 14.0.0
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
@ -6910,12 +7129,6 @@ snapshots:
|
|||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
pvtsutils@1.3.6:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
pvutils@1.1.3: {}
|
||||
|
||||
qs@6.14.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
@ -7058,8 +7271,6 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.40.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
rou3@0.5.1: {}
|
||||
|
||||
router@2.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
|
@ -7138,8 +7349,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
|
@ -7408,6 +7617,8 @@ snapshots:
|
|||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
toad-cache@3.7.0: {}
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
toucan-js@4.1.1:
|
||||
|
@ -7570,14 +7781,16 @@ snapshots:
|
|||
has-symbols: 1.1.0
|
||||
which-boxed-primitive: 1.1.1
|
||||
|
||||
uncrypto@0.1.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
unicorn-magic@0.1.0: {}
|
||||
|
||||
unicorn-magic@0.3.0: {}
|
||||
|
||||
universal-github-app-jwt@2.2.2: {}
|
||||
|
||||
universal-user-agent@7.0.3: {}
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
update-browserslist-db@1.1.3(browserslist@4.24.4):
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
- 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!!)
|
||||
|
||||
## License
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue