diff --git a/.changeset/oauth-provider-config-defaults.md b/.changeset/oauth-provider-config-defaults.md new file mode 100644 index 0000000..4e77d60 --- /dev/null +++ b/.changeset/oauth-provider-config-defaults.md @@ -0,0 +1,8 @@ +--- +'seamless-auth-api': patch +--- + +Apply OAuthProviderConfigSchema defaults to providers configured via OAUTH_PROVIDERS. The +env value was parsed with a raw JSON.parse, so per-provider fields like subjectJsonPath and +emailJsonPath stayed undefined and OAuth profile extraction failed with a generic +"OAuth login failed". The OAuth callback now also logs the underlying error. Fixes #49. diff --git a/AGENTS.md b/AGENTS.md index f9019d7..b5dcdf1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,6 +78,7 @@ Direct provider wiring currently lives in [src/config/directMessaging.ts](/Users - Be careful around token response shapes and bearer auth. Browser-cookie auth mode has been removed. - Preserve existing local worktree changes unless the user explicitly asks you to clean them up. - Keep comments minimal. Comment only when the code genuinely needs explaining (a non-obvious reason or gotcha); do not narrate what the code plainly does. +- Do not use em dashes (—) in public-facing text: commit messages, code comments, PR/issue descriptions, changesets, and docs. Use a comma, parentheses, or a separate sentence instead. ## Before You Finish A Change diff --git a/src/controllers/oauth.ts b/src/controllers/oauth.ts index 5883893..cdec4de 100644 --- a/src/controllers/oauth.ts +++ b/src/controllers/oauth.ts @@ -24,6 +24,9 @@ import { verifyOAuthState, } from '../services/oauthService.js'; import { issueSessionAndRespond } from '../services/sessionIssuance.js'; +import getLogger from '../utils/logger.js'; + +const logger = getLogger('oauth'); function allowedReturnTo(value: string | undefined, origins: string[]) { if (!value) return undefined; @@ -152,7 +155,8 @@ export async function finishOAuthLogin(req: Request, res: Response) { req, res, }); - } catch { + } catch (error) { + logger.error(`OAuth callback failed for provider ${provider.id}: ${error}`); await AuthEventService.log({ type: 'oauth_login_failed', req, diff --git a/src/utils/parseEnvConfigs.ts b/src/utils/parseEnvConfigs.ts index 1ddd825..13f4c3c 100644 --- a/src/utils/parseEnvConfigs.ts +++ b/src/utils/parseEnvConfigs.ts @@ -4,7 +4,10 @@ * See LICENSE file in the project root for full license information */ +import { z } from 'zod'; + import { SYSTEM_CONFIG_ENV_MAP } from '../config/systemConfig.envMap.js'; +import { OAuthProviderConfigSchema } from '../schemas/systemConfig.schema.js'; export function parseSystemConfigEnvValue(key: keyof typeof SYSTEM_CONFIG_ENV_MAP, raw: string) { switch (key) { @@ -18,6 +21,11 @@ export function parseSystemConfigEnvValue(key: keyof typeof SYSTEM_CONFIG_ENV_MA .filter(Boolean); case 'oauth_providers': + // Validate through the schema so per-provider defaults (subjectJsonPath, + // emailJsonPath, ...) are applied; raw JSON.parse leaves them undefined and + // OAuth profile extraction then silently fails. + return z.array(OAuthProviderConfigSchema).parse(JSON.parse(raw)); + case 'lockout_policy': return JSON.parse(raw); diff --git a/tests/unit/utils/parseSystemConfigEnvValue.spec.ts b/tests/unit/utils/parseSystemConfigEnvValue.spec.ts index 631238a..dc8c392 100644 --- a/tests/unit/utils/parseSystemConfigEnvValue.spec.ts +++ b/tests/unit/utils/parseSystemConfigEnvValue.spec.ts @@ -63,6 +63,40 @@ describe('parseSystemConfigEnvValue', () => { }); }); + describe('oauth_providers parsing', () => { + it('applies per-provider schema defaults (e.g. subjectJsonPath)', () => { + const raw = JSON.stringify([ + { + id: 'mock', + name: 'Mock', + clientId: 'client', + clientSecretEnv: 'MOCK_SECRET', + authorizationUrl: 'https://idp.test/authorize', + tokenUrl: 'https://idp.test/token', + userInfoUrl: 'https://idp.test/userinfo', + }, + ]); + + const result = parseSystemConfigEnvValue('oauth_providers', raw) as Array< + Record + >; + + expect(result[0]).toMatchObject({ + enabled: true, + subjectJsonPath: 'sub', + emailJsonPath: 'email', + emailVerifiedJsonPath: 'email_verified', + scopes: [], + allowSignup: true, + accountLinking: 'email', + }); + }); + + it('throws on an invalid provider entry', () => { + expect(() => parseSystemConfigEnvValue('oauth_providers', '[{"id":"x"}]')).toThrow(); + }); + }); + describe('invalid key', () => { it('throws for unknown key', () => { expect(() => parseSystemConfigEnvValue('invalid_key' as any, 'value')).toThrow(