kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: fix github auth flow
rodzic
f9254b5965
commit
ebd3e0928a
|
@ -0,0 +1,28 @@
|
|||
import type { DefaultHonoEnv } from '@agentic/platform-hono'
|
||||
import type { OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
|
||||
import { authStorage } from './utils'
|
||||
|
||||
export function registerV1AuthGitHubOAuthCallback(
|
||||
app: OpenAPIHono<DefaultHonoEnv>
|
||||
) {
|
||||
return app.get('auth/github/callback', async (c) => {
|
||||
const logger = c.get('logger')
|
||||
const query = c.req.query()
|
||||
|
||||
assert(query.state, 400, 'State is required')
|
||||
|
||||
const entry = await authStorage.get(['github', query.state, 'redirectUri'])
|
||||
assert(entry, 400, 'Redirect URI not found')
|
||||
const redirectUri = entry.redirectUri
|
||||
assert(entry.redirectUri, 400, 'Redirect URI not found')
|
||||
|
||||
const url = new URL(
|
||||
`${redirectUri}?${new URLSearchParams(query).toString()}`
|
||||
).toString()
|
||||
logger.info('GitHub auth callback', query, '=>', url)
|
||||
|
||||
return c.redirect(url)
|
||||
})
|
||||
}
|
|
@ -21,7 +21,7 @@ const route = createRoute({
|
|||
tags: ['auth'],
|
||||
operationId: 'exchangeOAuthCodeWithGitHub',
|
||||
method: 'post',
|
||||
path: 'auth/github',
|
||||
path: 'auth/github/exchange',
|
||||
security: openapiAuthenticatedSecuritySchemas,
|
||||
request: {
|
||||
body: {
|
||||
|
@ -51,7 +51,7 @@ const route = createRoute({
|
|||
}
|
||||
})
|
||||
|
||||
export function registerV1AuthExchangeOAuthCodeWithGitHub(
|
||||
export function registerV1AuthGitHubOAuthExchange(
|
||||
app: OpenAPIHono<DefaultHonoEnv>
|
||||
) {
|
||||
return app.openapi(route, async (c) => {
|
|
@ -0,0 +1,71 @@
|
|||
import type { DefaultHonoEnv } from '@agentic/platform-hono'
|
||||
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
|
||||
|
||||
import { env } from '@/lib/env'
|
||||
import {
|
||||
openapiAuthenticatedSecuritySchemas,
|
||||
openapiErrorResponse404,
|
||||
openapiErrorResponses
|
||||
} from '@/lib/openapi-utils'
|
||||
|
||||
import { authStorage } from './utils'
|
||||
|
||||
const route = createRoute({
|
||||
description: 'Starts a GitHub OAuth flow.',
|
||||
tags: ['auth'],
|
||||
operationId: 'initGitHubOAuthFlow',
|
||||
method: 'get',
|
||||
path: 'auth/github/init',
|
||||
security: openapiAuthenticatedSecuritySchemas,
|
||||
request: {
|
||||
query: z
|
||||
.object({
|
||||
redirect_uri: z.string(),
|
||||
client_id: z.string().optional(),
|
||||
scope: z.string().optional()
|
||||
})
|
||||
.passthrough()
|
||||
},
|
||||
responses: {
|
||||
302: {
|
||||
description: 'Redirected to GitHub'
|
||||
},
|
||||
...openapiErrorResponses,
|
||||
...openapiErrorResponse404
|
||||
}
|
||||
})
|
||||
|
||||
export function registerV1AuthGitHubOAuthInitFlow(
|
||||
app: OpenAPIHono<DefaultHonoEnv>
|
||||
) {
|
||||
return app.openapi(route, async (c) => {
|
||||
const logger = c.get('logger')
|
||||
const {
|
||||
client_id: clientId = env.GITHUB_CLIENT_ID,
|
||||
scope = 'user:email',
|
||||
redirect_uri: redirectUri
|
||||
} = c.req.query()
|
||||
|
||||
const state = crypto.randomUUID()
|
||||
|
||||
// TODO: unique identifier
|
||||
await authStorage.set(['github', state, 'redirectUri'], { redirectUri })
|
||||
|
||||
const publicRedirectUri = `${env.apiBaseUrl}/v1/auth/github/callback`
|
||||
|
||||
const url = new URL('https://github.com/login/oauth/authorize')
|
||||
url.searchParams.append('client_id', clientId)
|
||||
url.searchParams.append('scope', scope)
|
||||
url.searchParams.append('state', state)
|
||||
url.searchParams.append('redirect_uri', publicRedirectUri)
|
||||
|
||||
logger.info('Redirecting to GitHub', {
|
||||
url: url.toString(),
|
||||
clientId,
|
||||
scope,
|
||||
publicRedirectUri
|
||||
})
|
||||
|
||||
return c.redirect(url.toString())
|
||||
})
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { DrizzleAuthStorage } from '@/lib/drizzle-auth-storage'
|
||||
|
||||
export const authStorage = DrizzleAuthStorage()
|
|
@ -6,7 +6,9 @@ import type { AuthenticatedHonoEnv } from '@/lib/types'
|
|||
import * as middleware from '@/lib/middleware'
|
||||
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
|
||||
|
||||
import { registerV1AuthExchangeOAuthCodeWithGitHub } from './auth/github'
|
||||
import { registerV1AuthGitHubOAuthCallback } from './auth/github-callback'
|
||||
import { registerV1AuthGitHubOAuthExchange } from './auth/github-exchange'
|
||||
import { registerV1AuthGitHubOAuthInitFlow } from './auth/github-init'
|
||||
import { registerV1AuthSignInWithPassword } from './auth/sign-in-with-password'
|
||||
import { registerV1AuthSignUpWithPassword } from './auth/sign-up-with-password'
|
||||
import { registerV1AdminConsumersActivateConsumer } from './consumers/admin-activate-consumer'
|
||||
|
@ -78,7 +80,9 @@ registerHealthCheck(publicRouter)
|
|||
// Auth
|
||||
registerV1AuthSignInWithPassword(publicRouter)
|
||||
registerV1AuthSignUpWithPassword(publicRouter)
|
||||
registerV1AuthExchangeOAuthCodeWithGitHub(publicRouter)
|
||||
registerV1AuthGitHubOAuthExchange(publicRouter)
|
||||
registerV1AuthGitHubOAuthInitFlow(publicRouter)
|
||||
registerV1AuthGitHubOAuthCallback(publicRouter)
|
||||
|
||||
// Users
|
||||
registerV1UsersGetUser(privateRouter)
|
||||
|
|
|
@ -42,11 +42,15 @@ export function parseEnv(inputEnv: Record<string, unknown>) {
|
|||
)
|
||||
|
||||
const isStripeLive = env.STRIPE_SECRET_KEY.startsWith('sk_live_')
|
||||
const apiBaseUrl = baseEnv.isProd
|
||||
? 'https://api.agentic.so'
|
||||
: 'http://localhost:3001'
|
||||
|
||||
return {
|
||||
...baseEnv,
|
||||
...env,
|
||||
isStripeLive
|
||||
isStripeLive,
|
||||
apiBaseUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { env } from '@/lib/env'
|
|||
import * as middleware from '@/lib/middleware'
|
||||
|
||||
import { initExitHooks } from './lib/exit-hooks'
|
||||
import { registerOAuthRedirect } from './oauth-redirect'
|
||||
// import { registerOAuthRedirect } from './oauth-redirect'
|
||||
|
||||
export const app = new OpenAPIHono<DefaultHonoEnv>()
|
||||
|
||||
|
@ -32,7 +32,7 @@ app.use(middleware.accessLogger)
|
|||
app.use(middleware.responseTime)
|
||||
|
||||
// TODO: top-level auth routes
|
||||
registerOAuthRedirect(app)
|
||||
// registerOAuthRedirect(app)
|
||||
|
||||
// Mount all v1 API routes
|
||||
app.route('/v1', apiV1)
|
||||
|
|
|
@ -212,12 +212,10 @@ export class AgenticApiClient {
|
|||
scope?: string
|
||||
clientId?: string
|
||||
}): Promise<string> {
|
||||
const publicRedirectUri = `${this.apiBaseUrl}/oauth/callback?${new URLSearchParams({ uri: redirectUri }).toString()}`
|
||||
|
||||
const url = new URL('https://github.com/login/oauth/authorize')
|
||||
const url = new URL(`${this.apiBaseUrl}/v1/auth/github/init`)
|
||||
url.searchParams.append('client_id', clientId)
|
||||
url.searchParams.append('scope', scope)
|
||||
url.searchParams.append('redirect_uri', publicRedirectUri)
|
||||
url.searchParams.append('redirect_uri', redirectUri)
|
||||
|
||||
return url.toString()
|
||||
}
|
||||
|
@ -226,7 +224,9 @@ export class AgenticApiClient {
|
|||
async exchangeOAuthCodeWithGitHub(
|
||||
json: OperationBody<'exchangeOAuthCodeWithGitHub'>
|
||||
): Promise<AuthSession> {
|
||||
this._authSession = await this.ky.post('v1/auth/github', { json }).json()
|
||||
this._authSession = await this.ky
|
||||
.post('v1/auth/github/exchange', { json })
|
||||
.json()
|
||||
|
||||
this.onUpdateAuth?.(this._authSession)
|
||||
return this._authSession
|
||||
|
|
|
@ -55,7 +55,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/auth/github": {
|
||||
"/v1/auth/github/exchange": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
@ -64,7 +64,7 @@ export interface paths {
|
|||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** @description Exchanges GitHub code for auth session. */
|
||||
/** @description Exchanges a GitHub OAuth code for an Agentic auth session. */
|
||||
post: operations["exchangeOAuthCodeWithGitHub"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
|
@ -72,6 +72,23 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/auth/github/init": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** @description Starts a GitHub OAuth flow. */
|
||||
get: operations["initGitHubOAuthFlow"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/users/{userId}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
@ -1021,6 +1038,32 @@ export interface operations {
|
|||
404: components["responses"]["404"];
|
||||
};
|
||||
};
|
||||
initGitHubOAuthFlow: {
|
||||
parameters: {
|
||||
query: {
|
||||
redirect_uri: string;
|
||||
client_id?: string;
|
||||
scope?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Redirected to GitHub */
|
||||
302: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
400: components["responses"]["400"];
|
||||
401: components["responses"]["401"];
|
||||
403: components["responses"]["403"];
|
||||
404: components["responses"]["404"];
|
||||
};
|
||||
};
|
||||
getUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
Ładowanie…
Reference in New Issue