@happyvertical/auth 0.74.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +73 -0
- package/dist/chunks/cognito-dmypylFX.js +128 -0
- package/dist/chunks/cognito-dmypylFX.js.map +1 -0
- package/dist/chunks/decode_jwt-D2OK1b8a.js +1395 -0
- package/dist/chunks/decode_jwt-D2OK1b8a.js.map +1 -0
- package/dist/chunks/github-NSZp5tVm.js +413 -0
- package/dist/chunks/github-NSZp5tVm.js.map +1 -0
- package/dist/chunks/google-HXk2ctYR.js +483 -0
- package/dist/chunks/google-HXk2ctYR.js.map +1 -0
- package/dist/chunks/index-BpsMhFXS.js +151 -0
- package/dist/chunks/index-BpsMhFXS.js.map +1 -0
- package/dist/chunks/kanidm-hkw-YPVF.js +747 -0
- package/dist/chunks/kanidm-hkw-YPVF.js.map +1 -0
- package/dist/chunks/keycloak-t6JEUeOz.js +871 -0
- package/dist/chunks/keycloak-t6JEUeOz.js.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +499 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/errors.d.ts +227 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/factory.d.ts +85 -0
- package/dist/shared/factory.d.ts.map +1 -0
- package/dist/shared/providers/cognito.d.ts +38 -0
- package/dist/shared/providers/cognito.d.ts.map +1 -0
- package/dist/shared/providers/github.d.ts +65 -0
- package/dist/shared/providers/github.d.ts.map +1 -0
- package/dist/shared/providers/google.d.ts +58 -0
- package/dist/shared/providers/google.d.ts.map +1 -0
- package/dist/shared/providers/kanidm.d.ts +78 -0
- package/dist/shared/providers/kanidm.d.ts.map +1 -0
- package/dist/shared/providers/keycloak.d.ts +67 -0
- package/dist/shared/providers/keycloak.d.ts.map +1 -0
- package/dist/shared/providers/nostr/index.d.ts +47 -0
- package/dist/shared/providers/nostr/index.d.ts.map +1 -0
- package/dist/shared/types.d.ts +812 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/metadata.json +32 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-NSZp5tVm.js","sources":["../../src/shared/providers/github.ts"],"sourcesContent":["/**\n * GitHub Provider - OAuth2 Authentication\n *\n * Implements OAuth2 authentication with GitHub.\n * Note: GitHub does NOT support full OIDC (no id_token).\n * User info must be fetched via GitHub API.\n */\n\nimport {\n AccessDeniedError,\n ConfigurationError,\n InvalidClientError,\n InvalidGrantError,\n InvalidTokenError,\n NetworkError,\n NotImplementedError,\n ProviderError,\n} from '../errors.js';\nimport type {\n AuthCapabilities,\n AuthCredentials,\n AuthInterface,\n AuthorizationOptions,\n AuthorizationResult,\n AuthResult,\n CodeExchangeParams,\n CreateUserRequest,\n GitHubOptions,\n LogoutOptions,\n OIDCDiscoveryDocument,\n Session,\n TokenClaims,\n TokenIntrospection,\n TokenPayload,\n TokenValidationOptions,\n UserListResult,\n UserProfile,\n UserQuery,\n} from '../types.js';\n\n/**\n * Generate a random string for state.\n */\nfunction generateRandomString(length = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n );\n}\n\n/**\n * GitHub user response from API.\n */\ninterface GitHubUser {\n id: number;\n login: string;\n name: string | null;\n email: string | null;\n avatar_url: string;\n html_url: string;\n company: string | null;\n blog: string | null;\n location: string | null;\n bio: string | null;\n twitter_username: string | null;\n created_at: string;\n updated_at: string;\n}\n\n/**\n * GitHub email response from API.\n */\ninterface GitHubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: string | null;\n}\n\n/**\n * GitHub authentication provider.\n *\n * Implements OAuth2 authentication with GitHub.\n * Key differences from OIDC providers:\n * - No ID token - user info fetched via API\n * - No JWKS - tokens are opaque\n * - No token introspection endpoint\n */\nexport class GitHubProvider implements AuthInterface {\n private options: Required<Pick<GitHubOptions, 'clientId'>> & GitHubOptions;\n\n private static readonly AUTHORIZATION_URL =\n 'https://github.com/login/oauth/authorize';\n private static readonly TOKEN_URL =\n 'https://github.com/login/oauth/access_token';\n private static readonly API_URL = 'https://api.github.com';\n\n constructor(options: GitHubOptions) {\n if (!options.clientId) {\n throw new ConfigurationError('clientId is required', 'github');\n }\n if (!options.clientSecret) {\n throw new ConfigurationError('clientSecret is required', 'github');\n }\n\n this.options = {\n scopes: ['user:email', 'read:user'],\n timeout: 30000,\n maxRetries: 3,\n ...options,\n };\n }\n\n // ---------------------------------------------------------------------------\n // INTERNAL HELPERS\n // ---------------------------------------------------------------------------\n\n /**\n * Make an HTTP request to GitHub API with error handling.\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n token?: string,\n ): Promise<T> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': 'happyvertical-auth',\n ...this.options.headers,\n ...(options.headers as Record<string, string>),\n };\n\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: AbortSignal.timeout(this.options.timeout || 30000),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n let errorData: Record<string, unknown> = {};\n try {\n errorData = JSON.parse(errorBody);\n } catch {\n // Not JSON\n }\n\n this.handleHttpError(response.status, errorData, errorBody);\n }\n\n const text = await response.text();\n if (!text) return {} as T;\n return JSON.parse(text) as T;\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new NetworkError('Request timed out', 'github', error);\n }\n if (\n error instanceof AccessDeniedError ||\n error instanceof InvalidGrantError ||\n error instanceof InvalidClientError ||\n error instanceof ProviderError\n ) {\n throw error;\n }\n throw new NetworkError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'github',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Handle HTTP error responses.\n */\n private handleHttpError(\n status: number,\n data: Record<string, unknown>,\n rawBody: string,\n ): never {\n const error = data.error as string | undefined;\n const errorDescription = data.error_description || data.message || rawBody;\n\n switch (status) {\n case 400:\n if (error === 'bad_verification_code') {\n throw new InvalidGrantError('Invalid or expired code', 'github');\n }\n throw new ProviderError(`Bad request: ${errorDescription}`, 'github');\n\n case 401:\n throw new InvalidClientError('github');\n\n case 403:\n throw new AccessDeniedError(errorDescription as string, 'github');\n\n default:\n throw new ProviderError(\n `GitHub error (${status}): ${errorDescription}`,\n 'github',\n );\n }\n }\n\n /**\n * Fetch user info from GitHub API.\n */\n private async fetchUser(token: string): Promise<GitHubUser> {\n return this.request<GitHubUser>(\n `${GitHubProvider.API_URL}/user`,\n { method: 'GET' },\n token,\n );\n }\n\n /**\n * Fetch user emails from GitHub API.\n */\n private async fetchEmails(token: string): Promise<GitHubEmail[]> {\n return this.request<GitHubEmail[]>(\n `${GitHubProvider.API_URL}/user/emails`,\n { method: 'GET' },\n token,\n );\n }\n\n /**\n * Get the primary verified email for a user.\n */\n private async getPrimaryEmail(token: string): Promise<string | undefined> {\n try {\n const emails = await this.fetchEmails(token);\n const primary = emails.find((e) => e.primary && e.verified);\n return primary?.email;\n } catch {\n // Email scope may not be granted\n return undefined;\n }\n }\n\n // ---------------------------------------------------------------------------\n // AUTHENTICATION FLOWS\n // ---------------------------------------------------------------------------\n\n async getAuthorizationUrl(\n options?: AuthorizationOptions,\n ): Promise<AuthorizationResult> {\n const state = options?.state || generateRandomString();\n const scopes = options?.scopes ||\n this.options.scopes || ['user:email', 'read:user'];\n const redirectUri = options?.redirectUri || this.options.redirectUri;\n\n if (!redirectUri) {\n throw new ConfigurationError('redirectUri is required', 'github');\n }\n\n const params = new URLSearchParams({\n client_id: this.options.clientId,\n redirect_uri: redirectUri,\n scope: scopes.join(' '),\n state,\n });\n\n if (options?.loginHint) {\n params.set('login', options.loginHint);\n }\n\n // GitHub doesn't support PKCE for OAuth apps (only GitHub Apps)\n // But we can still pass nonce for state management\n const nonce = options?.nonce || generateRandomString();\n\n // Add extra params\n if (options?.extraParams) {\n for (const [key, value] of Object.entries(options.extraParams)) {\n params.set(key, value);\n }\n }\n\n const url = `${GitHubProvider.AUTHORIZATION_URL}?${params.toString()}`;\n\n return {\n url,\n state,\n nonce,\n // GitHub OAuth doesn't use PKCE\n codeVerifier: undefined,\n };\n }\n\n async exchangeCode(params: CodeExchangeParams): Promise<AuthResult> {\n const redirectUri = params.redirectUri || this.options.redirectUri;\n\n const body = new URLSearchParams({\n client_id: this.options.clientId,\n client_secret: this.options.clientSecret!,\n code: params.code,\n });\n\n if (redirectUri) {\n body.set('redirect_uri', redirectUri);\n }\n\n // GitHub token endpoint requires Accept: application/json\n const response = await fetch(GitHubProvider.TOKEN_URL, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n signal: AbortSignal.timeout(this.options.timeout || 30000),\n });\n\n const data = (await response.json()) as {\n access_token?: string;\n token_type?: string;\n scope?: string;\n error?: string;\n error_description?: string;\n };\n\n if (data.error) {\n if (data.error === 'bad_verification_code') {\n throw new InvalidGrantError(\n data.error_description || 'Invalid or expired code',\n 'github',\n );\n }\n throw new ProviderError(data.error_description || data.error, 'github');\n }\n\n if (!data.access_token) {\n throw new ProviderError('No access token received', 'github');\n }\n\n // Fetch user info to get user ID\n const user = await this.fetchUser(data.access_token);\n\n return {\n accessToken: data.access_token,\n tokenType: data.token_type || 'Bearer',\n expiresIn: 0, // GitHub tokens don't expire by default\n scope: data.scope,\n userId: user.id.toString(),\n // GitHub doesn't return refresh tokens or ID tokens\n };\n }\n\n async authenticate(_credentials: AuthCredentials): Promise<AuthResult> {\n throw new NotImplementedError('authenticate', 'github', {\n reason:\n 'GitHub only supports authorization code flow. Use getAuthorizationUrl() and exchangeCode() instead.',\n });\n }\n\n async refresh(_refreshToken: string): Promise<AuthResult> {\n throw new NotImplementedError('refresh', 'github', {\n reason:\n 'GitHub OAuth tokens do not expire and cannot be refreshed. For expiring tokens, use GitHub Apps instead.',\n });\n }\n\n async logout(options?: LogoutOptions): Promise<void> {\n // GitHub doesn't have a token revocation endpoint for OAuth apps\n // The user must revoke access via GitHub settings\n if (options?.token) {\n // Best effort: try to revoke via GitHub Apps API (won't work for OAuth apps)\n try {\n await fetch(\n `${GitHubProvider.API_URL}/applications/${this.options.clientId}/token`,\n {\n method: 'DELETE',\n headers: {\n Accept: 'application/json',\n Authorization: `Basic ${btoa(`${this.options.clientId}:${this.options.clientSecret}`)}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ access_token: options.token }),\n },\n );\n } catch {\n // Ignore - revocation may not be supported\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // TOKEN OPERATIONS\n // ---------------------------------------------------------------------------\n\n async validateToken(\n token: string,\n _options?: TokenValidationOptions,\n ): Promise<TokenClaims | null> {\n // GitHub tokens are opaque - validate by calling the API\n try {\n const user = await this.fetchUser(token);\n const email = await this.getPrimaryEmail(token);\n\n // Synthesize OIDC-like claims from GitHub user data\n return {\n sub: user.id.toString(),\n iss: 'https://github.com',\n aud: this.options.clientId,\n exp: 0, // GitHub tokens don't expire\n iat: Math.floor(Date.now() / 1000),\n email,\n email_verified: email ? true : undefined,\n preferred_username: user.login,\n name: user.name || undefined,\n picture: user.avatar_url,\n // GitHub-specific claims\n login: user.login,\n html_url: user.html_url,\n company: user.company,\n location: user.location,\n bio: user.bio,\n };\n } catch {\n return null;\n }\n }\n\n decodeToken(_token: string): TokenPayload {\n // GitHub tokens are opaque, not JWTs\n throw new InvalidTokenError(\n 'GitHub tokens are opaque and cannot be decoded',\n 'github',\n );\n }\n\n async introspectToken(token: string): Promise<TokenIntrospection> {\n const claims = await this.validateToken(token);\n return {\n active: claims !== null,\n claims: claims || undefined,\n };\n }\n\n // ---------------------------------------------------------------------------\n // USER OPERATIONS\n // ---------------------------------------------------------------------------\n\n async getProfile(tokenOrSession: string): Promise<UserProfile> {\n const user = await this.fetchUser(tokenOrSession);\n const email = await this.getPrimaryEmail(tokenOrSession);\n\n return {\n id: user.id.toString(),\n username: user.login,\n email,\n emailVerified: email ? true : undefined,\n displayName: user.name || user.login,\n picture: user.avatar_url,\n attributes: {\n html_url: user.html_url,\n company: user.company || '',\n blog: user.blog || '',\n location: user.location || '',\n bio: user.bio || '',\n twitter_username: user.twitter_username || '',\n },\n };\n }\n\n async updateProfile(\n _tokenOrSession: string,\n _profile: Partial<UserProfile>,\n ): Promise<UserProfile> {\n throw new NotImplementedError('updateProfile', 'github', {\n reason: 'GitHub does not support profile updates via OAuth2',\n });\n }\n\n async getUser(_userId: string, _adminToken?: string): Promise<UserProfile> {\n throw new NotImplementedError('getUser', 'github', {\n reason: 'GitHub does not expose admin user management',\n });\n }\n\n async createUser(\n _user: CreateUserRequest,\n _adminToken: string,\n ): Promise<UserProfile> {\n throw new NotImplementedError('createUser', 'github', {\n reason: 'GitHub does not expose user creation',\n });\n }\n\n async updateUser(\n _userId: string,\n _updates: Partial<CreateUserRequest>,\n _adminToken: string,\n ): Promise<UserProfile> {\n throw new NotImplementedError('updateUser', 'github', {\n reason: 'GitHub does not expose user management',\n });\n }\n\n async deleteUser(_userId: string, _adminToken: string): Promise<void> {\n throw new NotImplementedError('deleteUser', 'github', {\n reason: 'GitHub does not expose user management',\n });\n }\n\n async listUsers(\n _query: UserQuery,\n _adminToken?: string,\n ): Promise<UserListResult> {\n throw new NotImplementedError('listUsers', 'github', {\n reason: 'GitHub does not expose user listing',\n });\n }\n\n async requestPasswordReset(_email: string): Promise<void> {\n throw new NotImplementedError('requestPasswordReset', 'github', {\n reason: 'Password management is handled by GitHub',\n });\n }\n\n async resetPassword(_token: string, _newPassword: string): Promise<void> {\n throw new NotImplementedError('resetPassword', 'github', {\n reason: 'Password management is handled by GitHub',\n });\n }\n\n // ---------------------------------------------------------------------------\n // SESSION OPERATIONS\n // ---------------------------------------------------------------------------\n\n async listSessions(\n _userId: string,\n _adminToken?: string,\n ): Promise<Session[]> {\n throw new NotImplementedError('listSessions', 'github', {\n reason: 'Session management is handled by GitHub',\n });\n }\n\n async revokeSession(_sessionId: string, _adminToken?: string): Promise<void> {\n throw new NotImplementedError('revokeSession', 'github', {\n reason: 'Session management is handled by GitHub',\n });\n }\n\n async revokeAllSessions(\n _userId: string,\n _adminToken?: string,\n ): Promise<void> {\n throw new NotImplementedError('revokeAllSessions', 'github', {\n reason: 'Session management is handled by GitHub',\n });\n }\n\n // ---------------------------------------------------------------------------\n // AUTHORIZATION\n // ---------------------------------------------------------------------------\n\n async hasRole(_tokenOrUserId: string, _role: string): Promise<boolean> {\n return false;\n }\n\n async hasPermission(\n _tokenOrUserId: string,\n _permission: string,\n _resource?: string,\n ): Promise<boolean> {\n return false;\n }\n\n async getRoles(\n _tokenOrUserId: string,\n _adminToken?: string,\n ): Promise<string[]> {\n return [];\n }\n\n async assignRole(\n _userId: string,\n _role: string,\n _adminToken: string,\n ): Promise<void> {\n throw new NotImplementedError('assignRole', 'github', {\n reason: 'GitHub does not support role management',\n });\n }\n\n async removeRole(\n _userId: string,\n _role: string,\n _adminToken: string,\n ): Promise<void> {\n throw new NotImplementedError('removeRole', 'github', {\n reason: 'GitHub does not support role management',\n });\n }\n\n // ---------------------------------------------------------------------------\n // PROVIDER INFORMATION\n // ---------------------------------------------------------------------------\n\n async getCapabilities(): Promise<AuthCapabilities> {\n return {\n authorizationCode: true,\n passwordGrant: false,\n clientCredentials: false,\n tokenRefresh: false, // GitHub OAuth tokens don't expire\n oidc: false, // GitHub is OAuth2, not OIDC\n userManagement: false,\n sessionManagement: false,\n rbac: false,\n passwordReset: false,\n mfa: true, // GitHub supports 2FA\n socialLogin: true,\n federation: false,\n decentralized: false,\n };\n }\n\n async getDiscoveryDocument(): Promise<OIDCDiscoveryDocument | null> {\n // GitHub doesn't have OIDC discovery\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { ConfigurationError, NetworkError, AccessDeniedError, InvalidGrantError, InvalidClientError, ProviderError, NotImplementedError, InvalidNonceError, TokenExpiredError, InvalidTokenError } from "../index.js";
|
|
2
|
+
import { c as createRemoteJWKSet, d as decodeJwt, j as jwtVerify, J as JWTExpired, a as JWTClaimValidationFailed, b as JWSSignatureVerificationFailed } from "./decode_jwt-D2OK1b8a.js";
|
|
3
|
+
function generateRandomString(length = 32) {
|
|
4
|
+
const array = new Uint8Array(length);
|
|
5
|
+
crypto.getRandomValues(array);
|
|
6
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
7
|
+
""
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
async function generatePKCE() {
|
|
11
|
+
const verifier = generateRandomString(32);
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const data = encoder.encode(verifier);
|
|
14
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
15
|
+
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
16
|
+
return { verifier, challenge };
|
|
17
|
+
}
|
|
18
|
+
class GoogleProvider {
|
|
19
|
+
options;
|
|
20
|
+
discoveryDocument = null;
|
|
21
|
+
jwks = null;
|
|
22
|
+
static DISCOVERY_URL = "https://accounts.google.com/.well-known/openid-configuration";
|
|
23
|
+
constructor(options) {
|
|
24
|
+
if (!options.clientId) {
|
|
25
|
+
throw new ConfigurationError("clientId is required", "google");
|
|
26
|
+
}
|
|
27
|
+
if (!options.clientSecret) {
|
|
28
|
+
throw new ConfigurationError("clientSecret is required", "google");
|
|
29
|
+
}
|
|
30
|
+
this.options = {
|
|
31
|
+
usePKCE: true,
|
|
32
|
+
scopes: ["openid", "profile", "email"],
|
|
33
|
+
timeout: 3e4,
|
|
34
|
+
maxRetries: 3,
|
|
35
|
+
...options
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// INTERNAL HELPERS
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* Make an HTTP request with error handling.
|
|
43
|
+
*/
|
|
44
|
+
async request(url, options = {}, token) {
|
|
45
|
+
const headers = {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
...this.options.headers,
|
|
48
|
+
...options.headers
|
|
49
|
+
};
|
|
50
|
+
if (token) {
|
|
51
|
+
headers.Authorization = `Bearer ${token}`;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
...options,
|
|
56
|
+
headers,
|
|
57
|
+
signal: AbortSignal.timeout(this.options.timeout || 3e4)
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const errorBody = await response.text().catch(() => "");
|
|
61
|
+
let errorData = {};
|
|
62
|
+
try {
|
|
63
|
+
errorData = JSON.parse(errorBody);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
this.handleHttpError(response.status, errorData, errorBody);
|
|
67
|
+
}
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
if (!text) return {};
|
|
70
|
+
return JSON.parse(text);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
73
|
+
throw new NetworkError("Request timed out", "google", error);
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof AccessDeniedError || error instanceof InvalidGrantError || error instanceof InvalidClientError || error instanceof ProviderError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
throw new NetworkError(
|
|
79
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
80
|
+
"google",
|
|
81
|
+
error instanceof Error ? error : void 0
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Handle HTTP error responses.
|
|
87
|
+
*/
|
|
88
|
+
handleHttpError(status, data, rawBody) {
|
|
89
|
+
const error = data.error;
|
|
90
|
+
const errorDescription = data.error_description || data.message || rawBody;
|
|
91
|
+
switch (status) {
|
|
92
|
+
case 400:
|
|
93
|
+
if (error === "invalid_grant") {
|
|
94
|
+
throw new InvalidGrantError(errorDescription, "google");
|
|
95
|
+
}
|
|
96
|
+
if (error === "invalid_client") {
|
|
97
|
+
throw new InvalidClientError("google");
|
|
98
|
+
}
|
|
99
|
+
throw new ProviderError(`Bad request: ${errorDescription}`, "google");
|
|
100
|
+
case 401:
|
|
101
|
+
throw new InvalidClientError("google");
|
|
102
|
+
case 403:
|
|
103
|
+
throw new AccessDeniedError(errorDescription, "google");
|
|
104
|
+
default:
|
|
105
|
+
throw new ProviderError(
|
|
106
|
+
`Google error (${status}): ${errorDescription}`,
|
|
107
|
+
"google"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Fetch and cache the OIDC discovery document.
|
|
113
|
+
*/
|
|
114
|
+
async fetchDiscoveryDocument() {
|
|
115
|
+
if (this.discoveryDocument) {
|
|
116
|
+
return this.discoveryDocument;
|
|
117
|
+
}
|
|
118
|
+
this.discoveryDocument = await this.request(
|
|
119
|
+
GoogleProvider.DISCOVERY_URL
|
|
120
|
+
);
|
|
121
|
+
return this.discoveryDocument;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get JWKS for token validation.
|
|
125
|
+
*/
|
|
126
|
+
async getJWKS() {
|
|
127
|
+
if (this.jwks) {
|
|
128
|
+
return this.jwks;
|
|
129
|
+
}
|
|
130
|
+
const discovery = await this.fetchDiscoveryDocument();
|
|
131
|
+
this.jwks = createRemoteJWKSet(new URL(discovery.jwks_uri));
|
|
132
|
+
return this.jwks;
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// AUTHENTICATION FLOWS
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
async getAuthorizationUrl(options) {
|
|
138
|
+
const discovery = await this.fetchDiscoveryDocument();
|
|
139
|
+
const state = options?.state || generateRandomString();
|
|
140
|
+
const nonce = options?.nonce || generateRandomString();
|
|
141
|
+
const scopes = options?.scopes || this.options.scopes || ["openid", "profile", "email"];
|
|
142
|
+
const redirectUri = options?.redirectUri || this.options.redirectUri;
|
|
143
|
+
if (!redirectUri) {
|
|
144
|
+
throw new ConfigurationError("redirectUri is required", "google");
|
|
145
|
+
}
|
|
146
|
+
const params = new URLSearchParams({
|
|
147
|
+
client_id: this.options.clientId,
|
|
148
|
+
redirect_uri: redirectUri,
|
|
149
|
+
response_type: "code",
|
|
150
|
+
scope: scopes.join(" "),
|
|
151
|
+
state,
|
|
152
|
+
nonce,
|
|
153
|
+
access_type: "offline"
|
|
154
|
+
// Request refresh token
|
|
155
|
+
});
|
|
156
|
+
if (options?.prompt) {
|
|
157
|
+
params.set("prompt", options.prompt);
|
|
158
|
+
}
|
|
159
|
+
if (options?.loginHint) {
|
|
160
|
+
params.set("login_hint", options.loginHint);
|
|
161
|
+
}
|
|
162
|
+
if (options?.extraParams) {
|
|
163
|
+
for (const [key, value] of Object.entries(options.extraParams)) {
|
|
164
|
+
params.set(key, value);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
let codeVerifier;
|
|
168
|
+
if (this.options.usePKCE !== false) {
|
|
169
|
+
const pkce = await generatePKCE();
|
|
170
|
+
codeVerifier = pkce.verifier;
|
|
171
|
+
params.set("code_challenge", pkce.challenge);
|
|
172
|
+
params.set("code_challenge_method", "S256");
|
|
173
|
+
}
|
|
174
|
+
const url = `${discovery.authorization_endpoint}?${params.toString()}`;
|
|
175
|
+
return {
|
|
176
|
+
url,
|
|
177
|
+
state,
|
|
178
|
+
nonce,
|
|
179
|
+
codeVerifier
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async exchangeCode(params) {
|
|
183
|
+
const discovery = await this.fetchDiscoveryDocument();
|
|
184
|
+
const redirectUri = params.redirectUri || this.options.redirectUri;
|
|
185
|
+
if (!redirectUri) {
|
|
186
|
+
throw new ConfigurationError("redirectUri is required", "google");
|
|
187
|
+
}
|
|
188
|
+
const body = new URLSearchParams({
|
|
189
|
+
grant_type: "authorization_code",
|
|
190
|
+
client_id: this.options.clientId,
|
|
191
|
+
client_secret: this.options.clientSecret,
|
|
192
|
+
code: params.code,
|
|
193
|
+
redirect_uri: redirectUri
|
|
194
|
+
});
|
|
195
|
+
if (params.codeVerifier) {
|
|
196
|
+
body.set("code_verifier", params.codeVerifier);
|
|
197
|
+
}
|
|
198
|
+
const response = await this.request(discovery.token_endpoint, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
201
|
+
body: body.toString()
|
|
202
|
+
});
|
|
203
|
+
let userId = "";
|
|
204
|
+
if (response.id_token) {
|
|
205
|
+
const payload = decodeJwt(response.id_token);
|
|
206
|
+
userId = payload.sub || "";
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
accessToken: response.access_token,
|
|
210
|
+
tokenType: response.token_type || "Bearer",
|
|
211
|
+
expiresIn: response.expires_in,
|
|
212
|
+
refreshToken: response.refresh_token,
|
|
213
|
+
idToken: response.id_token,
|
|
214
|
+
scope: response.scope,
|
|
215
|
+
userId
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async authenticate(_credentials) {
|
|
219
|
+
throw new NotImplementedError("authenticate", "google", {
|
|
220
|
+
reason: "Google only supports authorization code flow. Use getAuthorizationUrl() and exchangeCode() instead."
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async refresh(refreshToken) {
|
|
224
|
+
const discovery = await this.fetchDiscoveryDocument();
|
|
225
|
+
const body = new URLSearchParams({
|
|
226
|
+
grant_type: "refresh_token",
|
|
227
|
+
client_id: this.options.clientId,
|
|
228
|
+
client_secret: this.options.clientSecret,
|
|
229
|
+
refresh_token: refreshToken
|
|
230
|
+
});
|
|
231
|
+
const response = await this.request(discovery.token_endpoint, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
234
|
+
body: body.toString()
|
|
235
|
+
});
|
|
236
|
+
let userId = "";
|
|
237
|
+
if (response.id_token) {
|
|
238
|
+
const payload = decodeJwt(response.id_token);
|
|
239
|
+
userId = payload.sub || "";
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
accessToken: response.access_token,
|
|
243
|
+
tokenType: response.token_type || "Bearer",
|
|
244
|
+
expiresIn: response.expires_in,
|
|
245
|
+
refreshToken: response.refresh_token || refreshToken,
|
|
246
|
+
idToken: response.id_token,
|
|
247
|
+
scope: response.scope,
|
|
248
|
+
userId
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async logout(options) {
|
|
252
|
+
if (options?.token) {
|
|
253
|
+
try {
|
|
254
|
+
await fetch(
|
|
255
|
+
`https://oauth2.googleapis.com/revoke?token=${options.token}`,
|
|
256
|
+
{ method: "POST" }
|
|
257
|
+
);
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (options?.refreshToken) {
|
|
262
|
+
try {
|
|
263
|
+
await fetch(
|
|
264
|
+
`https://oauth2.googleapis.com/revoke?token=${options.refreshToken}`,
|
|
265
|
+
{ method: "POST" }
|
|
266
|
+
);
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// TOKEN OPERATIONS
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
async validateToken(token, options) {
|
|
275
|
+
try {
|
|
276
|
+
const jwks = await this.getJWKS();
|
|
277
|
+
const discovery = await this.fetchDiscoveryDocument();
|
|
278
|
+
const verifyOptions = {
|
|
279
|
+
issuer: options?.issuer || discovery.issuer,
|
|
280
|
+
clockTolerance: options?.clockTolerance || 0
|
|
281
|
+
};
|
|
282
|
+
if (!options?.issuer) {
|
|
283
|
+
verifyOptions.issuer = [
|
|
284
|
+
"https://accounts.google.com",
|
|
285
|
+
"accounts.google.com"
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
if (options?.audience) {
|
|
289
|
+
verifyOptions.audience = options.audience;
|
|
290
|
+
}
|
|
291
|
+
const { payload } = await jwtVerify(token, jwks, verifyOptions);
|
|
292
|
+
if (options?.nonce && payload.nonce !== options.nonce) {
|
|
293
|
+
throw new InvalidNonceError("google");
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
sub: payload.sub || "",
|
|
297
|
+
iss: payload.iss || "",
|
|
298
|
+
aud: payload.aud || "",
|
|
299
|
+
exp: payload.exp || 0,
|
|
300
|
+
iat: payload.iat || 0,
|
|
301
|
+
nbf: payload.nbf,
|
|
302
|
+
azp: payload.azp,
|
|
303
|
+
email: payload.email,
|
|
304
|
+
email_verified: payload.email_verified,
|
|
305
|
+
name: payload.name,
|
|
306
|
+
picture: payload.picture,
|
|
307
|
+
given_name: payload.given_name,
|
|
308
|
+
family_name: payload.family_name,
|
|
309
|
+
locale: payload.locale,
|
|
310
|
+
...payload
|
|
311
|
+
};
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (error instanceof JWTExpired) {
|
|
314
|
+
throw new TokenExpiredError("google");
|
|
315
|
+
}
|
|
316
|
+
if (error instanceof JWTClaimValidationFailed) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
if (error instanceof JWSSignatureVerificationFailed) {
|
|
320
|
+
throw new InvalidTokenError("Invalid token signature", "google");
|
|
321
|
+
}
|
|
322
|
+
if (error instanceof InvalidNonceError) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
decodeToken(token) {
|
|
329
|
+
try {
|
|
330
|
+
const parts = token.split(".");
|
|
331
|
+
if (parts.length !== 3) {
|
|
332
|
+
throw new InvalidTokenError("Invalid JWT format", "google");
|
|
333
|
+
}
|
|
334
|
+
const header = JSON.parse(atob(parts[0]));
|
|
335
|
+
const payload = decodeJwt(token);
|
|
336
|
+
return {
|
|
337
|
+
header: {
|
|
338
|
+
alg: header.alg,
|
|
339
|
+
typ: header.typ,
|
|
340
|
+
kid: header.kid
|
|
341
|
+
},
|
|
342
|
+
payload,
|
|
343
|
+
signature: parts[2]
|
|
344
|
+
};
|
|
345
|
+
} catch {
|
|
346
|
+
throw new InvalidTokenError("Failed to decode token", "google");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async introspectToken(token) {
|
|
350
|
+
const claims = await this.validateToken(token);
|
|
351
|
+
return {
|
|
352
|
+
active: claims !== null,
|
|
353
|
+
claims: claims || void 0
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// USER OPERATIONS
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
async getProfile(tokenOrSession) {
|
|
360
|
+
const response = await this.request(
|
|
361
|
+
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
362
|
+
{ method: "GET" },
|
|
363
|
+
tokenOrSession
|
|
364
|
+
);
|
|
365
|
+
return {
|
|
366
|
+
id: response.sub,
|
|
367
|
+
email: response.email,
|
|
368
|
+
emailVerified: response.email_verified,
|
|
369
|
+
firstName: response.given_name,
|
|
370
|
+
lastName: response.family_name,
|
|
371
|
+
displayName: response.name,
|
|
372
|
+
picture: response.picture
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async updateProfile(_tokenOrSession, _profile) {
|
|
376
|
+
throw new NotImplementedError("updateProfile", "google", {
|
|
377
|
+
reason: "Google does not support profile updates via OAuth2"
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
async getUser(_userId, _adminToken) {
|
|
381
|
+
throw new NotImplementedError("getUser", "google", {
|
|
382
|
+
reason: "Google does not expose admin user management"
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
async createUser(_user, _adminToken) {
|
|
386
|
+
throw new NotImplementedError("createUser", "google", {
|
|
387
|
+
reason: "Google does not expose user creation"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async updateUser(_userId, _updates, _adminToken) {
|
|
391
|
+
throw new NotImplementedError("updateUser", "google", {
|
|
392
|
+
reason: "Google does not expose user management"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async deleteUser(_userId, _adminToken) {
|
|
396
|
+
throw new NotImplementedError("deleteUser", "google", {
|
|
397
|
+
reason: "Google does not expose user management"
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
async listUsers(_query, _adminToken) {
|
|
401
|
+
throw new NotImplementedError("listUsers", "google", {
|
|
402
|
+
reason: "Google does not expose user listing"
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async requestPasswordReset(_email) {
|
|
406
|
+
throw new NotImplementedError("requestPasswordReset", "google", {
|
|
407
|
+
reason: "Password management is handled by Google"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
async resetPassword(_token, _newPassword) {
|
|
411
|
+
throw new NotImplementedError("resetPassword", "google", {
|
|
412
|
+
reason: "Password management is handled by Google"
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
// SESSION OPERATIONS
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
async listSessions(_userId, _adminToken) {
|
|
419
|
+
throw new NotImplementedError("listSessions", "google", {
|
|
420
|
+
reason: "Session management is handled by Google"
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
async revokeSession(_sessionId, _adminToken) {
|
|
424
|
+
throw new NotImplementedError("revokeSession", "google", {
|
|
425
|
+
reason: "Session management is handled by Google"
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
async revokeAllSessions(_userId, _adminToken) {
|
|
429
|
+
throw new NotImplementedError("revokeAllSessions", "google", {
|
|
430
|
+
reason: "Session management is handled by Google"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// AUTHORIZATION
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
async hasRole(_tokenOrUserId, _role) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
async hasPermission(_tokenOrUserId, _permission, _resource) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
async getRoles(_tokenOrUserId, _adminToken) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
async assignRole(_userId, _role, _adminToken) {
|
|
446
|
+
throw new NotImplementedError("assignRole", "google", {
|
|
447
|
+
reason: "Google does not support role management"
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async removeRole(_userId, _role, _adminToken) {
|
|
451
|
+
throw new NotImplementedError("removeRole", "google", {
|
|
452
|
+
reason: "Google does not support role management"
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
// PROVIDER INFORMATION
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
async getCapabilities() {
|
|
459
|
+
return {
|
|
460
|
+
authorizationCode: true,
|
|
461
|
+
passwordGrant: false,
|
|
462
|
+
clientCredentials: false,
|
|
463
|
+
tokenRefresh: true,
|
|
464
|
+
oidc: true,
|
|
465
|
+
userManagement: false,
|
|
466
|
+
sessionManagement: false,
|
|
467
|
+
rbac: false,
|
|
468
|
+
passwordReset: false,
|
|
469
|
+
mfa: true,
|
|
470
|
+
// Google supports 2FA
|
|
471
|
+
socialLogin: true,
|
|
472
|
+
federation: false,
|
|
473
|
+
decentralized: false
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async getDiscoveryDocument() {
|
|
477
|
+
return this.fetchDiscoveryDocument();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
export {
|
|
481
|
+
GoogleProvider
|
|
482
|
+
};
|
|
483
|
+
//# sourceMappingURL=google-HXk2ctYR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-HXk2ctYR.js","sources":["../../src/shared/providers/google.ts"],"sourcesContent":["/**\n * Google Provider - OAuth2/OIDC Authentication\n *\n * Implements OAuth2/OIDC authentication with Google including:\n * - OIDC Discovery\n * - Authorization Code Flow with PKCE\n * - Token validation using JWKS (RS256)\n * - ID token claims extraction\n */\n\nimport * as jose from 'jose';\n\nimport {\n AccessDeniedError,\n ConfigurationError,\n InvalidClientError,\n InvalidGrantError,\n InvalidNonceError,\n InvalidTokenError,\n NetworkError,\n NotImplementedError,\n ProviderError,\n TokenExpiredError,\n} from '../errors.js';\nimport type {\n AuthCapabilities,\n AuthCredentials,\n AuthInterface,\n AuthorizationOptions,\n AuthorizationResult,\n AuthResult,\n CodeExchangeParams,\n CreateUserRequest,\n GoogleOptions,\n LogoutOptions,\n OIDCDiscoveryDocument,\n Session,\n TokenClaims,\n TokenIntrospection,\n TokenPayload,\n TokenValidationOptions,\n UserListResult,\n UserProfile,\n UserQuery,\n} from '../types.js';\n\n/**\n * Generate a random string for state/nonce/PKCE.\n */\nfunction generateRandomString(length = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n );\n}\n\n/**\n * Generate PKCE code verifier and challenge.\n */\nasync function generatePKCE(): Promise<{\n verifier: string;\n challenge: string;\n}> {\n const verifier = generateRandomString(32);\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n return { verifier, challenge };\n}\n\n/**\n * Google authentication provider.\n *\n * Implements OAuth2/OIDC authentication with Google.\n * Uses standard OIDC discovery and PKCE for security.\n */\nexport class GoogleProvider implements AuthInterface {\n private options: Required<Pick<GoogleOptions, 'clientId'>> & GoogleOptions;\n private discoveryDocument: OIDCDiscoveryDocument | null = null;\n private jwks: jose.JWTVerifyGetKey | null = null;\n\n private static readonly DISCOVERY_URL =\n 'https://accounts.google.com/.well-known/openid-configuration';\n\n constructor(options: GoogleOptions) {\n if (!options.clientId) {\n throw new ConfigurationError('clientId is required', 'google');\n }\n if (!options.clientSecret) {\n throw new ConfigurationError('clientSecret is required', 'google');\n }\n\n this.options = {\n usePKCE: true,\n scopes: ['openid', 'profile', 'email'],\n timeout: 30000,\n maxRetries: 3,\n ...options,\n };\n }\n\n // ---------------------------------------------------------------------------\n // INTERNAL HELPERS\n // ---------------------------------------------------------------------------\n\n /**\n * Make an HTTP request with error handling.\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n token?: string,\n ): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n ...(options.headers as Record<string, string>),\n };\n\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: AbortSignal.timeout(this.options.timeout || 30000),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n let errorData: Record<string, unknown> = {};\n try {\n errorData = JSON.parse(errorBody);\n } catch {\n // Not JSON\n }\n\n this.handleHttpError(response.status, errorData, errorBody);\n }\n\n const text = await response.text();\n if (!text) return {} as T;\n return JSON.parse(text) as T;\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new NetworkError('Request timed out', 'google', error);\n }\n if (\n error instanceof AccessDeniedError ||\n error instanceof InvalidGrantError ||\n error instanceof InvalidClientError ||\n error instanceof ProviderError\n ) {\n throw error;\n }\n throw new NetworkError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'google',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Handle HTTP error responses.\n */\n private handleHttpError(\n status: number,\n data: Record<string, unknown>,\n rawBody: string,\n ): never {\n const error = data.error as string | undefined;\n const errorDescription = data.error_description || data.message || rawBody;\n\n switch (status) {\n case 400:\n if (error === 'invalid_grant') {\n throw new InvalidGrantError(errorDescription as string, 'google');\n }\n if (error === 'invalid_client') {\n throw new InvalidClientError('google');\n }\n throw new ProviderError(`Bad request: ${errorDescription}`, 'google');\n\n case 401:\n throw new InvalidClientError('google');\n\n case 403:\n throw new AccessDeniedError(errorDescription as string, 'google');\n\n default:\n throw new ProviderError(\n `Google error (${status}): ${errorDescription}`,\n 'google',\n );\n }\n }\n\n /**\n * Fetch and cache the OIDC discovery document.\n */\n private async fetchDiscoveryDocument(): Promise<OIDCDiscoveryDocument> {\n if (this.discoveryDocument) {\n return this.discoveryDocument;\n }\n\n this.discoveryDocument = await this.request<OIDCDiscoveryDocument>(\n GoogleProvider.DISCOVERY_URL,\n );\n return this.discoveryDocument;\n }\n\n /**\n * Get JWKS for token validation.\n */\n private async getJWKS(): Promise<jose.JWTVerifyGetKey> {\n if (this.jwks) {\n return this.jwks;\n }\n\n const discovery = await this.fetchDiscoveryDocument();\n this.jwks = jose.createRemoteJWKSet(new URL(discovery.jwks_uri));\n return this.jwks;\n }\n\n // ---------------------------------------------------------------------------\n // AUTHENTICATION FLOWS\n // ---------------------------------------------------------------------------\n\n async getAuthorizationUrl(\n options?: AuthorizationOptions,\n ): Promise<AuthorizationResult> {\n const discovery = await this.fetchDiscoveryDocument();\n\n const state = options?.state || generateRandomString();\n const nonce = options?.nonce || generateRandomString();\n const scopes = options?.scopes ||\n this.options.scopes || ['openid', 'profile', 'email'];\n const redirectUri = options?.redirectUri || this.options.redirectUri;\n\n if (!redirectUri) {\n throw new ConfigurationError('redirectUri is required', 'google');\n }\n\n const params = new URLSearchParams({\n client_id: this.options.clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n nonce,\n access_type: 'offline', // Request refresh token\n });\n\n if (options?.prompt) {\n params.set('prompt', options.prompt);\n }\n\n if (options?.loginHint) {\n params.set('login_hint', options.loginHint);\n }\n\n // Add extra params\n if (options?.extraParams) {\n for (const [key, value] of Object.entries(options.extraParams)) {\n params.set(key, value);\n }\n }\n\n // PKCE\n let codeVerifier: string | undefined;\n if (this.options.usePKCE !== false) {\n const pkce = await generatePKCE();\n codeVerifier = pkce.verifier;\n params.set('code_challenge', pkce.challenge);\n params.set('code_challenge_method', 'S256');\n }\n\n const url = `${discovery.authorization_endpoint}?${params.toString()}`;\n\n return {\n url,\n state,\n nonce,\n codeVerifier,\n };\n }\n\n async exchangeCode(params: CodeExchangeParams): Promise<AuthResult> {\n const discovery = await this.fetchDiscoveryDocument();\n\n const redirectUri = params.redirectUri || this.options.redirectUri;\n if (!redirectUri) {\n throw new ConfigurationError('redirectUri is required', 'google');\n }\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.options.clientId,\n client_secret: this.options.clientSecret!,\n code: params.code,\n redirect_uri: redirectUri,\n });\n\n // Code verifier for PKCE\n if (params.codeVerifier) {\n body.set('code_verifier', params.codeVerifier);\n }\n\n const response = await this.request<{\n access_token: string;\n refresh_token?: string;\n id_token?: string;\n expires_in: number;\n token_type: string;\n scope?: string;\n }>(discovery.token_endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n // Decode ID token to get user ID\n let userId = '';\n if (response.id_token) {\n const payload = jose.decodeJwt(response.id_token);\n userId = payload.sub || '';\n }\n\n return {\n accessToken: response.access_token,\n tokenType: response.token_type || 'Bearer',\n expiresIn: response.expires_in,\n refreshToken: response.refresh_token,\n idToken: response.id_token,\n scope: response.scope,\n userId,\n };\n }\n\n async authenticate(_credentials: AuthCredentials): Promise<AuthResult> {\n throw new NotImplementedError('authenticate', 'google', {\n reason:\n 'Google only supports authorization code flow. Use getAuthorizationUrl() and exchangeCode() instead.',\n });\n }\n\n async refresh(refreshToken: string): Promise<AuthResult> {\n const discovery = await this.fetchDiscoveryDocument();\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.options.clientId,\n client_secret: this.options.clientSecret!,\n refresh_token: refreshToken,\n });\n\n const response = await this.request<{\n access_token: string;\n refresh_token?: string;\n id_token?: string;\n expires_in: number;\n token_type: string;\n scope?: string;\n }>(discovery.token_endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n // Decode to get user ID\n let userId = '';\n if (response.id_token) {\n const payload = jose.decodeJwt(response.id_token);\n userId = payload.sub || '';\n }\n\n return {\n accessToken: response.access_token,\n tokenType: response.token_type || 'Bearer',\n expiresIn: response.expires_in,\n refreshToken: response.refresh_token || refreshToken,\n idToken: response.id_token,\n scope: response.scope,\n userId,\n };\n }\n\n async logout(options?: LogoutOptions): Promise<void> {\n // Google uses token revocation endpoint\n if (options?.token) {\n try {\n await fetch(\n `https://oauth2.googleapis.com/revoke?token=${options.token}`,\n { method: 'POST' },\n );\n } catch {\n // Ignore revocation errors\n }\n }\n\n if (options?.refreshToken) {\n try {\n await fetch(\n `https://oauth2.googleapis.com/revoke?token=${options.refreshToken}`,\n { method: 'POST' },\n );\n } catch {\n // Ignore revocation errors\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // TOKEN OPERATIONS\n // ---------------------------------------------------------------------------\n\n async validateToken(\n token: string,\n options?: TokenValidationOptions,\n ): Promise<TokenClaims | null> {\n try {\n const jwks = await this.getJWKS();\n const discovery = await this.fetchDiscoveryDocument();\n\n const verifyOptions: jose.JWTVerifyOptions = {\n issuer: options?.issuer || discovery.issuer,\n clockTolerance: options?.clockTolerance || 0,\n };\n\n // Google tokens can have 'accounts.google.com' or 'https://accounts.google.com' as issuer\n if (!options?.issuer) {\n verifyOptions.issuer = [\n 'https://accounts.google.com',\n 'accounts.google.com',\n ];\n }\n\n if (options?.audience) {\n verifyOptions.audience = options.audience;\n }\n\n const { payload } = await jose.jwtVerify(token, jwks, verifyOptions);\n\n // Check nonce if provided\n if (options?.nonce && payload.nonce !== options.nonce) {\n throw new InvalidNonceError('google');\n }\n\n return {\n sub: payload.sub || '',\n iss: payload.iss || '',\n aud: payload.aud || '',\n exp: payload.exp || 0,\n iat: payload.iat || 0,\n nbf: payload.nbf,\n azp: payload.azp as string | undefined,\n email: payload.email as string | undefined,\n email_verified: payload.email_verified as boolean | undefined,\n name: payload.name as string | undefined,\n picture: payload.picture as string | undefined,\n given_name: payload.given_name as string | undefined,\n family_name: payload.family_name as string | undefined,\n locale: payload.locale as string | undefined,\n ...payload,\n };\n } catch (error) {\n if (error instanceof jose.errors.JWTExpired) {\n throw new TokenExpiredError('google');\n }\n if (error instanceof jose.errors.JWTClaimValidationFailed) {\n return null;\n }\n if (error instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new InvalidTokenError('Invalid token signature', 'google');\n }\n if (error instanceof InvalidNonceError) {\n throw error;\n }\n return null;\n }\n }\n\n decodeToken(token: string): TokenPayload {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) {\n throw new InvalidTokenError('Invalid JWT format', 'google');\n }\n\n const header = JSON.parse(atob(parts[0]));\n const payload = jose.decodeJwt(token);\n\n return {\n header: {\n alg: header.alg,\n typ: header.typ,\n kid: header.kid,\n },\n payload: payload as unknown as TokenClaims,\n signature: parts[2],\n };\n } catch {\n throw new InvalidTokenError('Failed to decode token', 'google');\n }\n }\n\n async introspectToken(token: string): Promise<TokenIntrospection> {\n // Google doesn't have a public introspection endpoint\n // Fall back to local validation\n const claims = await this.validateToken(token);\n return {\n active: claims !== null,\n claims: claims || undefined,\n };\n }\n\n // ---------------------------------------------------------------------------\n // USER OPERATIONS\n // ---------------------------------------------------------------------------\n\n async getProfile(tokenOrSession: string): Promise<UserProfile> {\n const response = await this.request<{\n sub: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n locale?: string;\n }>(\n 'https://www.googleapis.com/oauth2/v3/userinfo',\n { method: 'GET' },\n tokenOrSession,\n );\n\n return {\n id: response.sub,\n email: response.email,\n emailVerified: response.email_verified,\n firstName: response.given_name,\n lastName: response.family_name,\n displayName: response.name,\n picture: response.picture,\n };\n }\n\n async updateProfile(\n _tokenOrSession: string,\n _profile: Partial<UserProfile>,\n ): Promise<UserProfile> {\n throw new NotImplementedError('updateProfile', 'google', {\n reason: 'Google does not support profile updates via OAuth2',\n });\n }\n\n async getUser(_userId: string, _adminToken?: string): Promise<UserProfile> {\n throw new NotImplementedError('getUser', 'google', {\n reason: 'Google does not expose admin user management',\n });\n }\n\n async createUser(\n _user: CreateUserRequest,\n _adminToken: string,\n ): Promise<UserProfile> {\n throw new NotImplementedError('createUser', 'google', {\n reason: 'Google does not expose user creation',\n });\n }\n\n async updateUser(\n _userId: string,\n _updates: Partial<CreateUserRequest>,\n _adminToken: string,\n ): Promise<UserProfile> {\n throw new NotImplementedError('updateUser', 'google', {\n reason: 'Google does not expose user management',\n });\n }\n\n async deleteUser(_userId: string, _adminToken: string): Promise<void> {\n throw new NotImplementedError('deleteUser', 'google', {\n reason: 'Google does not expose user management',\n });\n }\n\n async listUsers(\n _query: UserQuery,\n _adminToken?: string,\n ): Promise<UserListResult> {\n throw new NotImplementedError('listUsers', 'google', {\n reason: 'Google does not expose user listing',\n });\n }\n\n async requestPasswordReset(_email: string): Promise<void> {\n throw new NotImplementedError('requestPasswordReset', 'google', {\n reason: 'Password management is handled by Google',\n });\n }\n\n async resetPassword(_token: string, _newPassword: string): Promise<void> {\n throw new NotImplementedError('resetPassword', 'google', {\n reason: 'Password management is handled by Google',\n });\n }\n\n // ---------------------------------------------------------------------------\n // SESSION OPERATIONS\n // ---------------------------------------------------------------------------\n\n async listSessions(\n _userId: string,\n _adminToken?: string,\n ): Promise<Session[]> {\n throw new NotImplementedError('listSessions', 'google', {\n reason: 'Session management is handled by Google',\n });\n }\n\n async revokeSession(_sessionId: string, _adminToken?: string): Promise<void> {\n throw new NotImplementedError('revokeSession', 'google', {\n reason: 'Session management is handled by Google',\n });\n }\n\n async revokeAllSessions(\n _userId: string,\n _adminToken?: string,\n ): Promise<void> {\n throw new NotImplementedError('revokeAllSessions', 'google', {\n reason: 'Session management is handled by Google',\n });\n }\n\n // ---------------------------------------------------------------------------\n // AUTHORIZATION\n // ---------------------------------------------------------------------------\n\n async hasRole(_tokenOrUserId: string, _role: string): Promise<boolean> {\n // Google doesn't have a concept of roles in OAuth\n return false;\n }\n\n async hasPermission(\n _tokenOrUserId: string,\n _permission: string,\n _resource?: string,\n ): Promise<boolean> {\n return false;\n }\n\n async getRoles(\n _tokenOrUserId: string,\n _adminToken?: string,\n ): Promise<string[]> {\n return [];\n }\n\n async assignRole(\n _userId: string,\n _role: string,\n _adminToken: string,\n ): Promise<void> {\n throw new NotImplementedError('assignRole', 'google', {\n reason: 'Google does not support role management',\n });\n }\n\n async removeRole(\n _userId: string,\n _role: string,\n _adminToken: string,\n ): Promise<void> {\n throw new NotImplementedError('removeRole', 'google', {\n reason: 'Google does not support role management',\n });\n }\n\n // ---------------------------------------------------------------------------\n // PROVIDER INFORMATION\n // ---------------------------------------------------------------------------\n\n async getCapabilities(): Promise<AuthCapabilities> {\n return {\n authorizationCode: true,\n passwordGrant: false,\n clientCredentials: false,\n tokenRefresh: true,\n oidc: true,\n userManagement: false,\n sessionManagement: false,\n rbac: false,\n passwordReset: false,\n mfa: true, // Google supports 2FA\n socialLogin: true,\n federation: false,\n decentralized: false,\n };\n }\n\n async getDiscoveryDocument(): Promise<OIDCDiscoveryDocument | null> {\n return this.fetchDiscoveryDocument();\n }\n}\n"],"names":["jose.createRemoteJWKSet","jose.decodeJwt","jose.jwtVerify","jose.errors.JWTExpired","jose.errors.JWTClaimValidationFailed","jose.errors.JWSSignatureVerificationFailed"],"mappings":"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"}
|