@cloudauth/providers 0.1.0

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/dist/google.js ADDED
@@ -0,0 +1,71 @@
1
+ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
2
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
3
+ const GOOGLE_USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
4
+ const DEFAULT_SCOPES = ['openid', 'email', 'profile'];
5
+ export function google(config) {
6
+ const scopes = config.scope ?? DEFAULT_SCOPES;
7
+ return {
8
+ id: 'google',
9
+ type: 'oidc',
10
+ authorizationUrl(params) {
11
+ const url = new URL(GOOGLE_AUTH_URL);
12
+ url.searchParams.set('client_id', config.clientId);
13
+ url.searchParams.set('redirect_uri', params.redirectUri);
14
+ url.searchParams.set('response_type', 'code');
15
+ url.searchParams.set('scope', scopes.join(' '));
16
+ url.searchParams.set('state', params.state);
17
+ url.searchParams.set('code_challenge', params.codeVerifier);
18
+ url.searchParams.set('code_challenge_method', 'S256');
19
+ url.searchParams.set('access_type', 'offline');
20
+ url.searchParams.set('prompt', 'consent');
21
+ return url;
22
+ },
23
+ async exchangeCode(params) {
24
+ const body = new URLSearchParams({
25
+ client_id: config.clientId,
26
+ client_secret: config.clientSecret,
27
+ code: params.code,
28
+ code_verifier: params.codeVerifier,
29
+ redirect_uri: params.redirectUri,
30
+ grant_type: 'authorization_code',
31
+ });
32
+ const response = await fetch(GOOGLE_TOKEN_URL, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
35
+ body,
36
+ });
37
+ if (!response.ok) {
38
+ const error = await response.text().catch(() => 'unknown error');
39
+ throw new Error(`Google token exchange failed: ${String(response.status)} ${error}`);
40
+ }
41
+ const data = (await response.json());
42
+ return {
43
+ accessToken: data.access_token ?? '',
44
+ refreshToken: data.refresh_token,
45
+ idToken: data.id_token,
46
+ scope: data.scope,
47
+ tokenType: data.token_type,
48
+ expiresAt: data.expires_in,
49
+ };
50
+ },
51
+ async getProfile(tokens) {
52
+ const response = await fetch(GOOGLE_USERINFO_URL, {
53
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
54
+ });
55
+ if (!response.ok) {
56
+ throw new Error(`Google userinfo failed: ${String(response.status)}`);
57
+ }
58
+ const data = (await response.json());
59
+ return {
60
+ provider: 'google',
61
+ providerAccountId: data.sub ?? '',
62
+ email: data.email,
63
+ emailVerified: data.email_verified,
64
+ name: data.name,
65
+ image: data.picture,
66
+ raw: data,
67
+ };
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=google.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../src/google.ts"],"names":[],"mappings":"AAeA,MAAM,eAAe,GAAG,8CAA8C,CAAA;AACtE,MAAM,gBAAgB,GAAG,qCAAqC,CAAA;AAC9D,MAAM,mBAAmB,GAAG,kDAAkD,CAAA;AAC9E,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;AAmBrD,MAAM,UAAU,MAAM,CAAC,MAAoB;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,cAAc,CAAA;IAE7C,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,MAAM;QAEZ,gBAAgB,CAAC,MAA2B;YAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAA;YACpC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;YACxD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,CAAA;YAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;YAC/C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;YAC3C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;YAC3D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAA;YACrD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;YAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;YACzC,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAA0B;YAC3C,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;gBAC/B,SAAS,EAAE,MAAM,CAAC,QAAQ;gBAC1B,aAAa,EAAE,MAAM,CAAC,YAAY;gBAClC,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,aAAa,EAAE,MAAM,CAAC,YAAY;gBAClC,YAAY,EAAE,MAAM,CAAC,WAAW;gBAChC,UAAU,EAAE,oBAAoB;aACjC,CAAC,CAAA;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gBAAgB,EAAE;gBAC7C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;gBAChE,IAAI;aACL,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAA;gBAChE,MAAM,IAAI,KAAK,CAAC,iCAAiC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;YACtF,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwB,CAAA;YAC3D,OAAO;gBACL,WAAW,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;gBACpC,YAAY,EAAE,IAAI,CAAC,aAAa;gBAChC,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,SAAS,EAAE,IAAI,CAAC,UAAU;gBAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;aAC3B,CAAA;QACH,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,MAAgB;YAC/B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,EAAE;gBAChD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE,EAAE;aAC3D,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACvE,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAA;YAEtD,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,iBAAiB,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;gBACjC,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,aAAa,EAAE,IAAI,CAAC,cAAc;gBAClC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,OAAO;gBACnB,GAAG,EAAE,IAAI;aACV,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../src/google.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export type { AuthProvider, AuthorizationParams, ExchangeCodeParams } from '@cloudauth/core';
2
+ export type { ProviderProfile, TokenSet } from '@cloudauth/core';
3
+ export { google } from './google.js';
4
+ export type { GoogleConfig } from './google.js';
5
+ export { github } from './github.js';
6
+ export type { GitHubConfig } from './github.js';
7
+ export { genericOAuth } from './generic-oauth.js';
8
+ export type { GenericOAuthConfig } from './generic-oauth.js';
9
+ export { createOIDCProvider } from './generic-oidc.js';
10
+ export type { GenericOIDCConfig } from './generic-oidc.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAEhE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAEhE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { google } from './google.js';
2
+ export { github } from './github.js';
3
+ export { genericOAuth } from './generic-oauth.js';
4
+ export { createOIDCProvider } from './generic-oidc.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@cloudauth/providers",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@cloudauth/core": "0.1.0"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "typecheck": "tsc --noEmit"
21
+ }
22
+ }
@@ -0,0 +1,95 @@
1
+ import type {
2
+ AuthProvider,
3
+ AuthorizationParams,
4
+ ExchangeCodeParams,
5
+ TokenSet,
6
+ ProviderProfile,
7
+ } from '@cloudauth/core'
8
+
9
+ export interface GenericOAuthConfig {
10
+ id: string
11
+ clientId: string
12
+ clientSecret: string
13
+ authorizationEndpoint: string
14
+ tokenEndpoint: string
15
+ userinfoEndpoint: string
16
+ redirectUri?: string
17
+ scopes?: string[]
18
+ mapProfile: (profile: Record<string, unknown>) => ProviderProfile
19
+ }
20
+
21
+ export function genericOAuth(config: GenericOAuthConfig): AuthProvider {
22
+ const scopes = config.scopes ?? ['profile']
23
+
24
+ return {
25
+ id: config.id,
26
+ type: 'oauth',
27
+
28
+ authorizationUrl(params: AuthorizationParams): URL {
29
+ const url = new URL(config.authorizationEndpoint)
30
+ url.searchParams.set('client_id', config.clientId)
31
+ url.searchParams.set('redirect_uri', params.redirectUri)
32
+ url.searchParams.set('response_type', 'code')
33
+ url.searchParams.set('scope', scopes.join(' '))
34
+ url.searchParams.set('state', params.state)
35
+ return url
36
+ },
37
+
38
+ async exchangeCode(params: ExchangeCodeParams): Promise<TokenSet> {
39
+ const body = new URLSearchParams({
40
+ client_id: config.clientId,
41
+ client_secret: config.clientSecret,
42
+ code: params.code,
43
+ redirect_uri: params.redirectUri,
44
+ grant_type: 'authorization_code',
45
+ })
46
+
47
+ const response = await fetch(config.tokenEndpoint, {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
50
+ body,
51
+ })
52
+
53
+ if (!response.ok) {
54
+ const error = await response.text().catch(() => 'unknown error')
55
+ throw new Error(`${config.id} token exchange failed: ${String(response.status)} ${error}`)
56
+ }
57
+
58
+ const data = (await response.json()) as {
59
+ access_token?: string
60
+ refresh_token?: string
61
+ id_token?: string
62
+ scope?: string
63
+ token_type?: string
64
+ expires_in?: number
65
+ }
66
+
67
+ return {
68
+ accessToken: data.access_token ?? '',
69
+ refreshToken: data.refresh_token,
70
+ idToken: data.id_token,
71
+ scope: data.scope,
72
+ tokenType: data.token_type,
73
+ expiresAt: data.expires_in,
74
+ }
75
+ },
76
+
77
+ async getProfile(tokens: TokenSet): Promise<ProviderProfile> {
78
+ const response = await fetch(config.userinfoEndpoint, {
79
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
80
+ })
81
+
82
+ if (!response.ok) {
83
+ throw new Error(`${config.id} userinfo failed: ${String(response.status)}`)
84
+ }
85
+
86
+ const data = (await response.json()) as Record<string, unknown>
87
+ const profile = config.mapProfile(data)
88
+ return {
89
+ ...profile,
90
+ provider: config.id,
91
+ raw: data,
92
+ }
93
+ },
94
+ }
95
+ }
@@ -0,0 +1,128 @@
1
+ import type {
2
+ AuthProvider,
3
+ AuthorizationParams,
4
+ ExchangeCodeParams,
5
+ TokenSet,
6
+ ProviderProfile,
7
+ } from '@cloudauth/core'
8
+
9
+ export interface GenericOIDCConfig {
10
+ id: string
11
+ clientId: string
12
+ clientSecret: string
13
+ issuer: string
14
+ redirectUri?: string
15
+ scopes?: string[]
16
+ mapProfile?: (profile: Record<string, unknown>) => Partial<ProviderProfile>
17
+ }
18
+
19
+ interface UserinfoResponse {
20
+ sub?: string
21
+ email?: string
22
+ email_verified?: boolean
23
+ name?: string
24
+ picture?: string
25
+ [key: string]: unknown
26
+ }
27
+
28
+ const DEFAULT_OIDC_SCOPES = ['openid', 'email', 'profile']
29
+
30
+ /**
31
+ * Create a generic OIDC provider.
32
+ * Uses the issuer URL to discover the authorization, token, and userinfo endpoints
33
+ * via the OIDC discovery document (/.well-known/openid-configuration).
34
+ */
35
+ export async function createOIDCProvider(config: GenericOIDCConfig): Promise<AuthProvider> {
36
+ const scopes = config.scopes ?? DEFAULT_OIDC_SCOPES
37
+
38
+ const discoveryUrl = `${config.issuer.replace(/\/+$/, '')}/.well-known/openid-configuration`
39
+ const discoveryResponse = await fetch(discoveryUrl)
40
+
41
+ if (!discoveryResponse.ok) {
42
+ throw new Error(`OIDC discovery failed for ${config.id}: ${String(discoveryResponse.status)}`)
43
+ }
44
+
45
+ const discovery = (await discoveryResponse.json()) as {
46
+ authorization_endpoint: string
47
+ token_endpoint: string
48
+ userinfo_endpoint: string
49
+ }
50
+
51
+ return {
52
+ id: config.id,
53
+ type: 'oidc',
54
+
55
+ authorizationUrl(params: AuthorizationParams): URL {
56
+ const url = new URL(discovery.authorization_endpoint)
57
+ url.searchParams.set('client_id', config.clientId)
58
+ url.searchParams.set('redirect_uri', params.redirectUri)
59
+ url.searchParams.set('response_type', 'code')
60
+ url.searchParams.set('scope', scopes.join(' '))
61
+ url.searchParams.set('state', params.state)
62
+ return url
63
+ },
64
+
65
+ async exchangeCode(params: ExchangeCodeParams): Promise<TokenSet> {
66
+ const body = new URLSearchParams({
67
+ client_id: config.clientId,
68
+ client_secret: config.clientSecret,
69
+ code: params.code,
70
+ redirect_uri: params.redirectUri,
71
+ grant_type: 'authorization_code',
72
+ })
73
+
74
+ const response = await fetch(discovery.token_endpoint, {
75
+ method: 'POST',
76
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
77
+ body,
78
+ })
79
+
80
+ if (!response.ok) {
81
+ const error = await response.text().catch(() => 'unknown error')
82
+ throw new Error(`${config.id} token exchange failed: ${String(response.status)} ${error}`)
83
+ }
84
+
85
+ const data = (await response.json()) as {
86
+ access_token?: string
87
+ refresh_token?: string
88
+ id_token?: string
89
+ scope?: string
90
+ token_type?: string
91
+ expires_in?: number
92
+ }
93
+
94
+ return {
95
+ accessToken: data.access_token ?? '',
96
+ refreshToken: data.refresh_token,
97
+ idToken: data.id_token,
98
+ scope: data.scope,
99
+ tokenType: data.token_type,
100
+ expiresAt: data.expires_in,
101
+ }
102
+ },
103
+
104
+ async getProfile(tokens: TokenSet): Promise<ProviderProfile> {
105
+ const response = await fetch(discovery.userinfo_endpoint, {
106
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
107
+ })
108
+
109
+ if (!response.ok) {
110
+ throw new Error(`${config.id} userinfo failed: ${String(response.status)}`)
111
+ }
112
+
113
+ const data = (await response.json()) as UserinfoResponse
114
+ const mapped = config.mapProfile?.(data) ?? {}
115
+
116
+ return {
117
+ provider: config.id,
118
+ providerAccountId: data.sub ?? '',
119
+ email: data.email,
120
+ emailVerified: data.email_verified,
121
+ name: data.name,
122
+ image: data.picture,
123
+ ...mapped,
124
+ raw: data,
125
+ }
126
+ },
127
+ }
128
+ }
package/src/github.ts ADDED
@@ -0,0 +1,134 @@
1
+ import type {
2
+ AuthProvider,
3
+ AuthorizationParams,
4
+ ExchangeCodeParams,
5
+ TokenSet,
6
+ ProviderProfile,
7
+ } from '@cloudauth/core'
8
+
9
+ export interface GitHubConfig {
10
+ clientId: string
11
+ clientSecret: string
12
+ redirectUri?: string
13
+ scope?: string[]
14
+ }
15
+
16
+ const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'
17
+ const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'
18
+ const GITHUB_USER_API = 'https://api.github.com/user'
19
+ const GITHUB_USER_EMAILS_API = 'https://api.github.com/user/emails'
20
+ const DEFAULT_SCOPES = ['read:user', 'user:email']
21
+
22
+ interface GitHubTokenResponse {
23
+ access_token?: string
24
+ refresh_token?: string
25
+ id_token?: string
26
+ scope?: string
27
+ token_type?: string
28
+ expires_in?: number
29
+ }
30
+
31
+ interface GitHubUser {
32
+ id?: number
33
+ login?: string
34
+ name?: string
35
+ avatar_url?: string
36
+ email?: string
37
+ }
38
+
39
+ interface GitHubEmail {
40
+ email: string
41
+ verified: boolean
42
+ primary: boolean
43
+ }
44
+
45
+ export function github(config: GitHubConfig): AuthProvider {
46
+ const scopes = config.scope ?? DEFAULT_SCOPES
47
+
48
+ return {
49
+ id: 'github',
50
+ type: 'oauth',
51
+
52
+ authorizationUrl(params: AuthorizationParams): URL {
53
+ const url = new URL(GITHUB_AUTH_URL)
54
+ url.searchParams.set('client_id', config.clientId)
55
+ url.searchParams.set('redirect_uri', params.redirectUri)
56
+ url.searchParams.set('scope', scopes.join(' '))
57
+ url.searchParams.set('state', params.state)
58
+ return url
59
+ },
60
+
61
+ async exchangeCode(params: ExchangeCodeParams): Promise<TokenSet> {
62
+ const body = new URLSearchParams({
63
+ client_id: config.clientId,
64
+ client_secret: config.clientSecret,
65
+ code: params.code,
66
+ redirect_uri: params.redirectUri,
67
+ })
68
+
69
+ const response = await fetch(GITHUB_TOKEN_URL, {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/x-www-form-urlencoded',
73
+ Accept: 'application/json',
74
+ },
75
+ body,
76
+ })
77
+
78
+ if (!response.ok) {
79
+ const error = await response.text().catch(() => 'unknown error')
80
+ throw new Error(`GitHub token exchange failed: ${String(response.status)} ${error}`)
81
+ }
82
+
83
+ const data = (await response.json()) as GitHubTokenResponse
84
+ return {
85
+ accessToken: data.access_token ?? '',
86
+ refreshToken: data.refresh_token,
87
+ idToken: data.id_token,
88
+ scope: data.scope,
89
+ tokenType: data.token_type,
90
+ expiresAt: data.expires_in,
91
+ }
92
+ },
93
+
94
+ async getProfile(tokens: TokenSet): Promise<ProviderProfile> {
95
+ const userResponse = await fetch(GITHUB_USER_API, {
96
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
97
+ })
98
+
99
+ if (!userResponse.ok) {
100
+ throw new Error(`GitHub user API failed: ${String(userResponse.status)}`)
101
+ }
102
+
103
+ const userData = (await userResponse.json()) as GitHubUser
104
+
105
+ let email = userData.email
106
+ let emailVerified = Boolean(userData.email)
107
+
108
+ if (!email) {
109
+ const emailsResponse = await fetch(GITHUB_USER_EMAILS_API, {
110
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
111
+ })
112
+
113
+ if (emailsResponse.ok) {
114
+ const emails = (await emailsResponse.json()) as GitHubEmail[]
115
+ const primaryEmail = emails.find((e) => e.primary)
116
+ if (primaryEmail) {
117
+ email = primaryEmail.email
118
+ emailVerified = primaryEmail.verified
119
+ }
120
+ }
121
+ }
122
+
123
+ return {
124
+ provider: 'github',
125
+ providerAccountId: userData.id != null ? String(userData.id) : '',
126
+ email,
127
+ emailVerified,
128
+ name: userData.name ?? userData.login,
129
+ image: userData.avatar_url,
130
+ raw: { ...userData, emails: undefined },
131
+ }
132
+ },
133
+ }
134
+ }
package/src/google.ts ADDED
@@ -0,0 +1,113 @@
1
+ import type {
2
+ AuthProvider,
3
+ AuthorizationParams,
4
+ ExchangeCodeParams,
5
+ TokenSet,
6
+ ProviderProfile,
7
+ } from '@cloudauth/core'
8
+
9
+ export interface GoogleConfig {
10
+ clientId: string
11
+ clientSecret: string
12
+ redirectUri?: string
13
+ scope?: string[]
14
+ }
15
+
16
+ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
17
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'
18
+ const GOOGLE_USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo'
19
+ const DEFAULT_SCOPES = ['openid', 'email', 'profile']
20
+
21
+ interface GoogleTokenResponse {
22
+ access_token?: string
23
+ refresh_token?: string
24
+ id_token?: string
25
+ scope?: string
26
+ token_type?: string
27
+ expires_in?: number
28
+ }
29
+
30
+ interface GoogleUserInfo {
31
+ sub?: string
32
+ email?: string
33
+ email_verified?: boolean
34
+ name?: string
35
+ picture?: string
36
+ }
37
+
38
+ export function google(config: GoogleConfig): AuthProvider {
39
+ const scopes = config.scope ?? DEFAULT_SCOPES
40
+
41
+ return {
42
+ id: 'google',
43
+ type: 'oidc',
44
+
45
+ authorizationUrl(params: AuthorizationParams): URL {
46
+ const url = new URL(GOOGLE_AUTH_URL)
47
+ url.searchParams.set('client_id', config.clientId)
48
+ url.searchParams.set('redirect_uri', params.redirectUri)
49
+ url.searchParams.set('response_type', 'code')
50
+ url.searchParams.set('scope', scopes.join(' '))
51
+ url.searchParams.set('state', params.state)
52
+ url.searchParams.set('code_challenge', params.codeVerifier)
53
+ url.searchParams.set('code_challenge_method', 'S256')
54
+ url.searchParams.set('access_type', 'offline')
55
+ url.searchParams.set('prompt', 'consent')
56
+ return url
57
+ },
58
+
59
+ async exchangeCode(params: ExchangeCodeParams): Promise<TokenSet> {
60
+ const body = new URLSearchParams({
61
+ client_id: config.clientId,
62
+ client_secret: config.clientSecret,
63
+ code: params.code,
64
+ code_verifier: params.codeVerifier,
65
+ redirect_uri: params.redirectUri,
66
+ grant_type: 'authorization_code',
67
+ })
68
+
69
+ const response = await fetch(GOOGLE_TOKEN_URL, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
72
+ body,
73
+ })
74
+
75
+ if (!response.ok) {
76
+ const error = await response.text().catch(() => 'unknown error')
77
+ throw new Error(`Google token exchange failed: ${String(response.status)} ${error}`)
78
+ }
79
+
80
+ const data = (await response.json()) as GoogleTokenResponse
81
+ return {
82
+ accessToken: data.access_token ?? '',
83
+ refreshToken: data.refresh_token,
84
+ idToken: data.id_token,
85
+ scope: data.scope,
86
+ tokenType: data.token_type,
87
+ expiresAt: data.expires_in,
88
+ }
89
+ },
90
+
91
+ async getProfile(tokens: TokenSet): Promise<ProviderProfile> {
92
+ const response = await fetch(GOOGLE_USERINFO_URL, {
93
+ headers: { Authorization: `Bearer ${tokens.accessToken}` },
94
+ })
95
+
96
+ if (!response.ok) {
97
+ throw new Error(`Google userinfo failed: ${String(response.status)}`)
98
+ }
99
+
100
+ const data = (await response.json()) as GoogleUserInfo
101
+
102
+ return {
103
+ provider: 'google',
104
+ providerAccountId: data.sub ?? '',
105
+ email: data.email,
106
+ emailVerified: data.email_verified,
107
+ name: data.name,
108
+ image: data.picture,
109
+ raw: data,
110
+ }
111
+ },
112
+ }
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type { AuthProvider, AuthorizationParams, ExchangeCodeParams } from '@cloudauth/core'
2
+ export type { ProviderProfile, TokenSet } from '@cloudauth/core'
3
+
4
+ export { google } from './google.js'
5
+ export type { GoogleConfig } from './google.js'
6
+
7
+ export { github } from './github.js'
8
+ export type { GitHubConfig } from './github.js'
9
+
10
+ export { genericOAuth } from './generic-oauth.js'
11
+ export type { GenericOAuthConfig } from './generic-oauth.js'
12
+
13
+ export { createOIDCProvider } from './generic-oidc.js'
14
+ export type { GenericOIDCConfig } from './generic-oidc.js'
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "declaration": true
8
+ },
9
+ "include": ["src"],
10
+ "references": [{ "path": "../core" }]
11
+ }