feat: integrate Supabase OAuth with OIDC discovery for authentication

Add rowboat auth flow using Supabase as the OIDC provider. User info is
fetched via the standard OIDC userinfo endpoint (discovered from issuer
metadata) instead of a hard-coded Supabase URL. Includes login screen,
auth state hook, IPC handlers, logout button, and id_token_sub persistence
for userinfo fetches across app restarts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-02-03 07:25:33 +05:30
parent 84c101fa21
commit bbe82c124d
10 changed files with 368 additions and 8 deletions

View file

@ -159,13 +159,16 @@ export function buildAuthorizationUrl(
state: string;
}
): URL {
return client.buildAuthorizationUrl(config, {
const url = client.buildAuthorizationUrl(config, {
redirect_uri: params.redirectUri,
scope: params.scope,
code_challenge: params.codeChallenge,
code_challenge_method: 'S256',
state: params.state,
});
console.log(`[OAuth] Authorization URL: ${url}`);
return url;
}
/**
@ -176,7 +179,7 @@ export async function exchangeCodeForTokens(
callbackUrl: URL,
codeVerifier: string,
expectedState: string
): Promise<OAuthTokens> {
): Promise<{ tokens: OAuthTokens; sub?: string }> {
console.log(`[OAuth] Exchanging authorization code for tokens...`);
const response = await client.authorizationCodeGrant(config, callbackUrl, {
@ -184,8 +187,27 @@ export async function exchangeCodeForTokens(
expectedState,
});
const claims = response.claims();
console.log(`[OAuth] Token exchange successful`);
return toOAuthTokens(response);
return {
tokens: toOAuthTokens(response),
sub: claims?.sub,
};
}
/**
* Fetch user info from the OIDC userinfo endpoint (discovered via issuer metadata)
*/
export async function fetchUserInfo(
config: client.Configuration,
accessToken: string,
expectedSubject: string
): Promise<{ email: string; name?: string }> {
const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject);
return {
email: userInfo.email ?? '',
name: userInfo.name,
};
}
/**

View file

@ -77,7 +77,22 @@ const providerConfigs: ProviderConfig = {
'profile',
'email',
]
}
},
rowboat: {
discovery: {
mode: 'issuer',
issuer: 'https://yhafoahozylbdyyyqjep.supabase.co/auth/v1',
},
client: {
mode: 'static',
clientId: '0b8a99ec-b5b2-4ddf-8e14-69a3a1675114',
},
scopes: [
'openid',
'email',
'profile',
],
},
};
/**

View file

@ -9,6 +9,7 @@ export const OAuthTokens = z.object({
expires_at: z.number(), // Unix timestamp
token_type: z.literal('Bearer').optional(),
scopes: z.array(z.string()).optional(), // Granted scopes from OAuth response
id_token_sub: z.string().optional(), // Subject claim from ID token
});
export type OAuthTokens = z.infer<typeof OAuthTokens>;