@shware/security 5.0.0 → 5.0.1

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.
@@ -22,6 +22,7 @@ async function exchangeAuthorizationCode(params) {
22
22
  body.append("grant_type", "authorization_code");
23
23
  if (params.pkce) body.append("code_verifier", params.pkce.code_verifier);
24
24
  const headers = {
25
+ ...params.headers,
25
26
  Accept: "application/json",
26
27
  "Content-Type": "application/x-www-form-urlencoded"
27
28
  };
@@ -1 +1 @@
1
- {"version":3,"file":"common.cjs","names":["OAuth2Error","createRemoteJWKSet","jwtVerify"],"sources":["../../../src/oauth2/provider/common.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from 'jose';\nimport { OAuth2Error } from '../error';\nimport type { ExchangeCodeParams, PkceParameters, RefreshTokenParams } from '../types';\n\nexport function createAuthorizationUri(options: {\n state: string;\n scope: string[];\n clientId: string;\n redirectUri: string;\n authorizationUri: string;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}) {\n const url = new URL(options.authorizationUri);\n url.searchParams.append('response_type', 'code');\n url.searchParams.append('client_id', options.clientId);\n url.searchParams.append('state', options.state);\n url.searchParams.append('scope', options.scope.join(' '));\n url.searchParams.append('redirect_uri', options.redirectUri);\n if (options.pkce) {\n url.searchParams.append('code_challenge_method', options.pkce.code_challenge_method);\n url.searchParams.append('code_challenge', options.pkce.code_challenge);\n }\n return url;\n}\n\nexport async function exchangeAuthorizationCode(\n params: ExchangeCodeParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('code', params.code);\n body.append('redirect_uri', params.redirectUri);\n body.append('grant_type', 'authorization_code');\n if (params.pkce) body.append('code_verifier', params.pkce.code_verifier);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n // Public clients (Google's installed-app clients, PKCE-only clients) have no secret.\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\nexport async function refreshAccessToken(\n params: RefreshTokenParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('grant_type', 'refresh_token');\n body.append('refresh_token', params.refreshToken);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\ninterface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n tokenRevokeUri: string;\n authentication?: 'basic' | 'post';\n}\n\nexport async function revokeToken(params: RevokeTokenParams) {\n const body = new URLSearchParams();\n body.append('token', params.token);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n const response = await fetch(params.tokenRevokeUri, { method: 'POST', headers, body });\n if (!response.ok) {\n const error = await response.json();\n console.error('Refresh token error:', error);\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to revoke token');\n }\n}\n\nexport interface VerifyIdTokenOptions {\n jwkSetUri: string;\n /** The `iss` values the provider signs with; anything else is refused. */\n issuer: string | string[];\n /**\n * The client the token must be issued to. An id token proves who the user is only to the client\n * it names in `aud`: any other app on the same identity provider can obtain a validly signed one\n * for the same user, so a token for a foreign client must never sign anyone in here.\n */\n audience: string;\n}\n\nexport async function verifyIdToken<T>(idToken: string, options: VerifyIdTokenOptions) {\n const { jwkSetUri, issuer, audience } = options;\n try {\n const jwks = createRemoteJWKSet(new URL(jwkSetUri));\n const { payload } = await jwtVerify(idToken, jwks, { issuer, audience });\n return payload as T;\n } catch (error) {\n console.error('Failed to verify id_token', error);\n throw new OAuth2Error(400, 'invalid_request', 'Failed to verify id_token');\n }\n}\n"],"mappings":";;;;AAIA,SAAgB,uBAAuB,SAOpC;CACD,MAAM,MAAM,IAAI,IAAI,QAAQ,gBAAgB;CAC5C,IAAI,aAAa,OAAO,iBAAiB,MAAM;CAC/C,IAAI,aAAa,OAAO,aAAa,QAAQ,QAAQ;CACrD,IAAI,aAAa,OAAO,SAAS,QAAQ,KAAK;CAC9C,IAAI,aAAa,OAAO,SAAS,QAAQ,MAAM,KAAK,GAAG,CAAC;CACxD,IAAI,aAAa,OAAO,gBAAgB,QAAQ,WAAW;CAC3D,IAAI,QAAQ,MAAM;EAChB,IAAI,aAAa,OAAO,yBAAyB,QAAQ,KAAK,qBAAqB;EACnF,IAAI,aAAa,OAAO,kBAAkB,QAAQ,KAAK,cAAc;CACvE;CACA,OAAO;AACT;AAEA,eAAsB,0BACpB,QAIA;CACA,MAAM,OAAO,IAAI,gBAAgB;CACjC,KAAK,OAAO,QAAQ,OAAO,IAAI;CAC/B,KAAK,OAAO,gBAAgB,OAAO,WAAW;CAC9C,KAAK,OAAO,cAAc,oBAAoB;CAC9C,IAAI,OAAO,MAAM,KAAK,OAAO,iBAAiB,OAAO,KAAK,aAAa;CAEvE,MAAM,UAAkC;EACtC,QAAQ;EACR,gBAAgB;CAClB;CACA,IAAI,OAAO,mBAAmB,SAE5B,QAAQ,gBAAgB,SADV,OAAO,KAAK,GAAG,OAAO,SAAS,GAAG,OAAO,gBAAgB,IAAI,CAAC,CAAC,SAAS,QACjD;MAChC;EACL,KAAK,OAAO,aAAa,OAAO,QAAQ;EAExC,IAAI,OAAO,cAAc,KAAK,OAAO,iBAAiB,OAAO,YAAY;CAC3E;CAEA,OAAO,MAAM,OAAO,UAAU;EAAE,QAAQ;EAAQ;EAAS;CAAK,CAAC;AACjE;AAEA,eAAsB,mBACpB,QAIA;CACA,MAAM,OAAO,IAAI,gBAAgB;CACjC,KAAK,OAAO,cAAc,eAAe;CACzC,KAAK,OAAO,iBAAiB,OAAO,YAAY;CAEhD,MAAM,UAAkC;EACtC,QAAQ;EACR,gBAAgB;CAClB;CAEA,IAAI,OAAO,mBAAmB,SAE5B,QAAQ,gBAAgB,SADV,OAAO,KAAK,GAAG,OAAO,SAAS,GAAG,OAAO,gBAAgB,IAAI,CAAC,CAAC,SAAS,QACjD;MAChC;EACL,KAAK,OAAO,aAAa,OAAO,QAAQ;EACxC,IAAI,OAAO,cAAc,KAAK,OAAO,iBAAiB,OAAO,YAAY;CAC3E;CAEA,OAAO,MAAM,OAAO,UAAU;EAAE,QAAQ;EAAQ;EAAS;CAAK,CAAC;AACjE;AAUA,eAAsB,YAAY,QAA2B;CAC3D,MAAM,OAAO,IAAI,gBAAgB;CACjC,KAAK,OAAO,SAAS,OAAO,KAAK;CACjC,MAAM,UAAkC;EACtC,QAAQ;EACR,gBAAgB;CAClB;CAEA,IAAI,OAAO,mBAAmB,SAE5B,QAAQ,gBAAgB,SADV,OAAO,KAAK,GAAG,OAAO,SAAS,GAAG,OAAO,gBAAgB,IAAI,CAAC,CAAC,SAAS,QACjD;MAChC;EACL,KAAK,OAAO,aAAa,OAAO,QAAQ;EACxC,IAAI,OAAO,cAAc,KAAK,OAAO,iBAAiB,OAAO,YAAY;CAC3E;CAEA,MAAM,WAAW,MAAM,MAAM,OAAO,gBAAgB;EAAE,QAAQ;EAAQ;EAAS;CAAK,CAAC;CACrF,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,QAAQ,MAAM,SAAS,KAAK;EAClC,QAAQ,MAAM,wBAAwB,KAAK;EAC3C,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,mBAAmB,wBAAwB;CACpF;AACF;AAcA,eAAsB,cAAiB,SAAiB,SAA+B;CACrF,MAAM,EAAE,WAAW,QAAQ,aAAa;CACxC,IAAI;EACF,MAAM,QAAA,GAAOC,KAAAA,mBAAAA,CAAmB,IAAI,IAAI,SAAS,CAAC;EAClD,MAAM,EAAE,YAAY,OAAA,GAAMC,KAAAA,UAAAA,CAAU,SAAS,MAAM;GAAE;GAAQ;EAAS,CAAC;EACvE,OAAO;CACT,SAAS,OAAO;EACd,QAAQ,MAAM,6BAA6B,KAAK;EAChD,MAAM,IAAIF,qBAAAA,YAAY,KAAK,mBAAmB,2BAA2B;CAC3E;AACF"}
1
+ {"version":3,"file":"common.cjs","names":["OAuth2Error","createRemoteJWKSet","jwtVerify"],"sources":["../../../src/oauth2/provider/common.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from 'jose';\nimport { OAuth2Error } from '../error';\nimport type { ExchangeCodeParams, PkceParameters, RefreshTokenParams } from '../types';\n\nexport function createAuthorizationUri(options: {\n state: string;\n scope: string[];\n clientId: string;\n redirectUri: string;\n authorizationUri: string;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}) {\n const url = new URL(options.authorizationUri);\n url.searchParams.append('response_type', 'code');\n url.searchParams.append('client_id', options.clientId);\n url.searchParams.append('state', options.state);\n url.searchParams.append('scope', options.scope.join(' '));\n url.searchParams.append('redirect_uri', options.redirectUri);\n if (options.pkce) {\n url.searchParams.append('code_challenge_method', options.pkce.code_challenge_method);\n url.searchParams.append('code_challenge', options.pkce.code_challenge);\n }\n return url;\n}\n\nexport async function exchangeAuthorizationCode(\n params: ExchangeCodeParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n /** Extra request headers some providers require, e.g. GitHub's `User-Agent`. */\n headers?: Record<string, string>;\n }\n) {\n const body = new URLSearchParams();\n body.append('code', params.code);\n body.append('redirect_uri', params.redirectUri);\n body.append('grant_type', 'authorization_code');\n if (params.pkce) body.append('code_verifier', params.pkce.code_verifier);\n\n const headers: Record<string, string> = {\n ...params.headers,\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n // Public clients (Google's installed-app clients, PKCE-only clients) have no secret.\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\nexport async function refreshAccessToken(\n params: RefreshTokenParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('grant_type', 'refresh_token');\n body.append('refresh_token', params.refreshToken);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\ninterface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n tokenRevokeUri: string;\n authentication?: 'basic' | 'post';\n}\n\nexport async function revokeToken(params: RevokeTokenParams) {\n const body = new URLSearchParams();\n body.append('token', params.token);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n const response = await fetch(params.tokenRevokeUri, { method: 'POST', headers, body });\n if (!response.ok) {\n const error = await response.json();\n console.error('Refresh token error:', error);\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to revoke token');\n }\n}\n\nexport interface VerifyIdTokenOptions {\n jwkSetUri: string;\n /** The `iss` values the provider signs with; anything else is refused. */\n issuer: string | string[];\n /**\n * The client the token must be issued to. An id token proves who the user is only to the client\n * it names in `aud`: any other app on the same identity provider can obtain a validly signed one\n * for the same user, so a token for a foreign client must never sign anyone in here.\n */\n audience: string;\n}\n\nexport async function verifyIdToken<T>(idToken: string, options: VerifyIdTokenOptions) {\n const { jwkSetUri, issuer, audience } = options;\n try {\n const jwks = createRemoteJWKSet(new URL(jwkSetUri));\n const { payload } = await jwtVerify(idToken, jwks, { issuer, audience });\n return payload as T;\n } catch (error) {\n console.error('Failed to verify id_token', error);\n throw new OAuth2Error(400, 'invalid_request', 'Failed to verify id_token');\n }\n}\n"],"mappings":"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"}
@@ -11,6 +11,8 @@ export declare function createAuthorizationUri(options: {
11
11
  export declare function exchangeAuthorizationCode(params: ExchangeCodeParams & {
12
12
  tokenUri: string;
13
13
  authentication?: 'basic' | 'post';
14
+ /** Extra request headers some providers require, e.g. GitHub's `User-Agent`. */
15
+ headers?: Record<string, string>;
14
16
  }): Promise<Response>;
15
17
  export declare function refreshAccessToken(params: RefreshTokenParams & {
16
18
  tokenUri: string;
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.cts","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"mappings":";;wBAIgB,uBAAuB;EACrC;EACA;EACA;EACA;EACA;EACA,OAAO,KAAK;IACb;wBAcqB,0BACpB,QAAQ;EACN;EACA;IACD,QAAA;wBAwBmB,mBACpB,QAAQ;EACN;EACA;IACD,QAAA;UAsBO;EACR;EACA;EACA;EACA;EACA;;wBAGoB,YAAY,QAAQ,oBAAiB;iBAwB1C;EACf;;EAEA;;;;;;EAMA;;wBAGoB,cAAc,GAAG,iBAAiB,SAAS,uBAAoB,QAAA"}
1
+ {"version":3,"file":"common.d.cts","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"mappings":";;wBAIgB,uBAAuB;EACrC;EACA;EACA;EACA;EACA;EACA,OAAO,KAAK;IACb;wBAcqB,0BACpB,QAAQ;EACN;EACA;;EAEA,UAAU;IACX,QAAA;wBAyBmB,mBACpB,QAAQ;EACN;EACA;IACD,QAAA;UAsBO;EACR;EACA;EACA;EACA;EACA;;wBAGoB,YAAY,QAAQ,oBAAiB;iBAwB1C;EACf;;EAEA;;;;;;EAMA;;wBAGoB,cAAc,GAAG,iBAAiB,SAAS,uBAAoB,QAAA"}
@@ -11,6 +11,8 @@ export declare function createAuthorizationUri(options: {
11
11
  export declare function exchangeAuthorizationCode(params: ExchangeCodeParams & {
12
12
  tokenUri: string;
13
13
  authentication?: 'basic' | 'post';
14
+ /** Extra request headers some providers require, e.g. GitHub's `User-Agent`. */
15
+ headers?: Record<string, string>;
14
16
  }): Promise<Response>;
15
17
  export declare function refreshAccessToken(params: RefreshTokenParams & {
16
18
  tokenUri: string;
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.mts","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"mappings":";;wBAIgB,uBAAuB;EACrC;EACA;EACA;EACA;EACA;EACA,OAAO,KAAK;IACb;wBAcqB,0BACpB,QAAQ;EACN;EACA;IACD,QAAA;wBAwBmB,mBACpB,QAAQ;EACN;EACA;IACD,QAAA;UAsBO;EACR;EACA;EACA;EACA;EACA;;wBAGoB,YAAY,QAAQ,oBAAiB;iBAwB1C;EACf;;EAEA;;;;;;EAMA;;wBAGoB,cAAc,GAAG,iBAAiB,SAAS,uBAAoB,QAAA"}
1
+ {"version":3,"file":"common.d.mts","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"mappings":";;wBAIgB,uBAAuB;EACrC;EACA;EACA;EACA;EACA;EACA,OAAO,KAAK;IACb;wBAcqB,0BACpB,QAAQ;EACN;EACA;;EAEA,UAAU;IACX,QAAA;wBAyBmB,mBACpB,QAAQ;EACN;EACA;IACD,QAAA;UAsBO;EACR;EACA;EACA;EACA;EACA;;wBAGoB,YAAY,QAAQ,oBAAiB;iBAwB1C;EACf;;EAEA;;;;;;EAMA;;wBAGoB,cAAc,GAAG,iBAAiB,SAAS,uBAAoB,QAAA"}
@@ -21,6 +21,7 @@ async function exchangeAuthorizationCode(params) {
21
21
  body.append("grant_type", "authorization_code");
22
22
  if (params.pkce) body.append("code_verifier", params.pkce.code_verifier);
23
23
  const headers = {
24
+ ...params.headers,
24
25
  Accept: "application/json",
25
26
  "Content-Type": "application/x-www-form-urlencoded"
26
27
  };
@@ -1 +1 @@
1
- {"version":3,"file":"common.mjs","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from 'jose';\nimport { OAuth2Error } from '../error';\nimport type { ExchangeCodeParams, PkceParameters, RefreshTokenParams } from '../types';\n\nexport function createAuthorizationUri(options: {\n state: string;\n scope: string[];\n clientId: string;\n redirectUri: string;\n authorizationUri: string;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}) {\n const url = new URL(options.authorizationUri);\n url.searchParams.append('response_type', 'code');\n url.searchParams.append('client_id', options.clientId);\n url.searchParams.append('state', options.state);\n url.searchParams.append('scope', options.scope.join(' '));\n url.searchParams.append('redirect_uri', options.redirectUri);\n if (options.pkce) {\n url.searchParams.append('code_challenge_method', options.pkce.code_challenge_method);\n url.searchParams.append('code_challenge', options.pkce.code_challenge);\n }\n return url;\n}\n\nexport async function exchangeAuthorizationCode(\n params: ExchangeCodeParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('code', params.code);\n body.append('redirect_uri', params.redirectUri);\n body.append('grant_type', 'authorization_code');\n if (params.pkce) body.append('code_verifier', params.pkce.code_verifier);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n // Public clients (Google's installed-app clients, PKCE-only clients) have no secret.\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\nexport async function refreshAccessToken(\n params: RefreshTokenParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('grant_type', 'refresh_token');\n body.append('refresh_token', params.refreshToken);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\ninterface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n tokenRevokeUri: string;\n authentication?: 'basic' | 'post';\n}\n\nexport async function revokeToken(params: RevokeTokenParams) {\n const body = new URLSearchParams();\n body.append('token', params.token);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n const response = await fetch(params.tokenRevokeUri, { method: 'POST', headers, body });\n if (!response.ok) {\n const error = await response.json();\n console.error('Refresh token error:', error);\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to revoke token');\n }\n}\n\nexport interface VerifyIdTokenOptions {\n jwkSetUri: string;\n /** The `iss` values the provider signs with; anything else is refused. */\n issuer: string | string[];\n /**\n * The client the token must be issued to. An id token proves who the user is only to the client\n * it names in `aud`: any other app on the same identity provider can obtain a validly signed one\n * for the same user, so a token for a foreign client must never sign anyone in here.\n */\n audience: string;\n}\n\nexport async function verifyIdToken<T>(idToken: string, options: VerifyIdTokenOptions) {\n const { jwkSetUri, issuer, audience } = options;\n try {\n const jwks = createRemoteJWKSet(new URL(jwkSetUri));\n const { payload } = await jwtVerify(idToken, jwks, { issuer, audience });\n return payload as T;\n } catch (error) {\n console.error('Failed to verify id_token', error);\n throw new OAuth2Error(400, 'invalid_request', 'Failed to verify id_token');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"common.mjs","names":[],"sources":["../../../src/oauth2/provider/common.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from 'jose';\nimport { OAuth2Error } from '../error';\nimport type { ExchangeCodeParams, PkceParameters, RefreshTokenParams } from '../types';\n\nexport function createAuthorizationUri(options: {\n state: string;\n scope: string[];\n clientId: string;\n redirectUri: string;\n authorizationUri: string;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}) {\n const url = new URL(options.authorizationUri);\n url.searchParams.append('response_type', 'code');\n url.searchParams.append('client_id', options.clientId);\n url.searchParams.append('state', options.state);\n url.searchParams.append('scope', options.scope.join(' '));\n url.searchParams.append('redirect_uri', options.redirectUri);\n if (options.pkce) {\n url.searchParams.append('code_challenge_method', options.pkce.code_challenge_method);\n url.searchParams.append('code_challenge', options.pkce.code_challenge);\n }\n return url;\n}\n\nexport async function exchangeAuthorizationCode(\n params: ExchangeCodeParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n /** Extra request headers some providers require, e.g. GitHub's `User-Agent`. */\n headers?: Record<string, string>;\n }\n) {\n const body = new URLSearchParams();\n body.append('code', params.code);\n body.append('redirect_uri', params.redirectUri);\n body.append('grant_type', 'authorization_code');\n if (params.pkce) body.append('code_verifier', params.pkce.code_verifier);\n\n const headers: Record<string, string> = {\n ...params.headers,\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n // Public clients (Google's installed-app clients, PKCE-only clients) have no secret.\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\nexport async function refreshAccessToken(\n params: RefreshTokenParams & {\n tokenUri: string;\n authentication?: 'basic' | 'post';\n }\n) {\n const body = new URLSearchParams();\n body.append('grant_type', 'refresh_token');\n body.append('refresh_token', params.refreshToken);\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n return fetch(params.tokenUri, { method: 'POST', headers, body });\n}\n\ninterface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n tokenRevokeUri: string;\n authentication?: 'basic' | 'post';\n}\n\nexport async function revokeToken(params: RevokeTokenParams) {\n const body = new URLSearchParams();\n body.append('token', params.token);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (params.authentication === 'basic') {\n const token = Buffer.from(`${params.clientId}:${params.clientSecret ?? ''}`).toString('base64');\n headers.Authorization = `Basic ${token}`;\n } else {\n body.append('client_id', params.clientId);\n if (params.clientSecret) body.append('client_secret', params.clientSecret);\n }\n\n const response = await fetch(params.tokenRevokeUri, { method: 'POST', headers, body });\n if (!response.ok) {\n const error = await response.json();\n console.error('Refresh token error:', error);\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to revoke token');\n }\n}\n\nexport interface VerifyIdTokenOptions {\n jwkSetUri: string;\n /** The `iss` values the provider signs with; anything else is refused. */\n issuer: string | string[];\n /**\n * The client the token must be issued to. An id token proves who the user is only to the client\n * it names in `aud`: any other app on the same identity provider can obtain a validly signed one\n * for the same user, so a token for a foreign client must never sign anyone in here.\n */\n audience: string;\n}\n\nexport async function verifyIdToken<T>(idToken: string, options: VerifyIdTokenOptions) {\n const { jwkSetUri, issuer, audience } = options;\n try {\n const jwks = createRemoteJWKSet(new URL(jwkSetUri));\n const { payload } = await jwtVerify(idToken, jwks, { issuer, audience });\n return payload as T;\n } catch (error) {\n console.error('Failed to verify id_token', error);\n throw new OAuth2Error(400, 'invalid_request', 'Failed to verify id_token');\n }\n}\n"],"mappings":"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"}
@@ -3,6 +3,18 @@ const require_oauth2_error = require("../error.cjs");
3
3
  const require_oauth2_provider_common = require("./common.cjs");
4
4
  let _shware_utils = require("@shware/utils");
5
5
  //#region src/oauth2/provider/github.ts
6
+ /**
7
+ * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends
8
+ * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.
9
+ * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required
10
+ */
11
+ const USER_AGENT = "@shware/security";
12
+ /** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */
13
+ async function describeFailure(response, message) {
14
+ const body = await response.text().catch(() => "");
15
+ const detail = body ? `: ${body.slice(0, 200)}` : "";
16
+ return `${message} (HTTP ${response.status})${detail}`;
17
+ }
6
18
  function createGithubProvider() {
7
19
  return {
8
20
  authorizationUri: "https://github.com/login/oauth/authorize",
@@ -20,26 +32,36 @@ function createGithubProvider() {
20
32
  async exchangeAuthorizationCode({ pkce: _, ...params }) {
21
33
  const response = await require_oauth2_provider_common.exchangeAuthorizationCode({
22
34
  ...params,
23
- tokenUri: this.tokenUri
35
+ tokenUri: this.tokenUri,
36
+ headers: { "User-Agent": USER_AGENT }
24
37
  });
25
38
  if (!response.ok) {
26
39
  const { error, error_description } = await response.json();
27
40
  throw new require_oauth2_error.OAuth2Error(response.status, error, error_description);
28
41
  }
29
- return await response.json();
42
+ const body = await response.json();
43
+ if (body.error) throw new require_oauth2_error.OAuth2Error(400, body.error, body.error_description);
44
+ return body;
30
45
  },
31
46
  async getUserInfo({ access_token }) {
32
47
  (0, _shware_utils.invariant)(access_token, "access_token is required");
33
48
  (0, _shware_utils.invariant)(this.userInfoUri, "userInfoUri is required");
34
49
  const headers = {
35
50
  Accept: "application/json",
36
- Authorization: `Bearer ${access_token}`
51
+ Authorization: `Bearer ${access_token}`,
52
+ "User-Agent": USER_AGENT
37
53
  };
38
54
  const response = await fetch(this.userInfoUri, { headers });
39
- if (!response.ok) throw new require_oauth2_error.OAuth2Error(response.status, "invalid_request", "Failed to fetch user info");
55
+ if (!response.ok) {
56
+ const message = await describeFailure(response, "Failed to fetch user info");
57
+ throw new require_oauth2_error.OAuth2Error(response.status, "invalid_request", message);
58
+ }
40
59
  const data = await response.json();
41
60
  const emailResponse = await fetch("https://api.github.com/user/emails", { headers });
42
- if (!emailResponse.ok) throw new require_oauth2_error.OAuth2Error(emailResponse.status, "invalid_request", "Failed to fetch user email");
61
+ if (!emailResponse.ok) {
62
+ const message = await describeFailure(emailResponse, "Failed to fetch user email");
63
+ throw new require_oauth2_error.OAuth2Error(emailResponse.status, "invalid_request", message);
64
+ }
43
65
  const emails = await emailResponse.json();
44
66
  data.email = emails.find((email) => email.primary)?.email || data.email;
45
67
  const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;
@@ -1 +1 @@
1
- {"version":3,"file":"github.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error"],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GithubToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to fetch user info');\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n throw new OAuth2Error(\n emailResponse.status,\n 'invalid_request',\n 'Failed to fetch user email'\n );\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;AAKA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GACtF,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IACZ,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,mBAAmB,2BAA2B;GAEvF,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IACjB,MAAM,IAAIA,qBAAAA,YACR,cAAc,QACd,mBACA,4BACF;GAEF,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"github.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error"],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAIA,qBAAAA,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAIA,qBAAAA,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.cts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";;wBAKgB,wBAAwB;qBA2D3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"github.d.cts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";;wBAmBgB,wBAAwB;qBAqE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.mts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";;wBAKgB,wBAAwB;qBA2D3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"github.d.mts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";;wBAmBgB,wBAAwB;qBAqE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
@@ -2,6 +2,18 @@ import { OAuth2Error } from "../error.mjs";
2
2
  import { createAuthorizationUri, exchangeAuthorizationCode } from "./common.mjs";
3
3
  import { invariant } from "@shware/utils";
4
4
  //#region src/oauth2/provider/github.ts
5
+ /**
6
+ * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends
7
+ * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.
8
+ * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required
9
+ */
10
+ const USER_AGENT = "@shware/security";
11
+ /** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */
12
+ async function describeFailure(response, message) {
13
+ const body = await response.text().catch(() => "");
14
+ const detail = body ? `: ${body.slice(0, 200)}` : "";
15
+ return `${message} (HTTP ${response.status})${detail}`;
16
+ }
5
17
  function createGithubProvider() {
6
18
  return {
7
19
  authorizationUri: "https://github.com/login/oauth/authorize",
@@ -19,26 +31,36 @@ function createGithubProvider() {
19
31
  async exchangeAuthorizationCode({ pkce: _, ...params }) {
20
32
  const response = await exchangeAuthorizationCode({
21
33
  ...params,
22
- tokenUri: this.tokenUri
34
+ tokenUri: this.tokenUri,
35
+ headers: { "User-Agent": USER_AGENT }
23
36
  });
24
37
  if (!response.ok) {
25
38
  const { error, error_description } = await response.json();
26
39
  throw new OAuth2Error(response.status, error, error_description);
27
40
  }
28
- return await response.json();
41
+ const body = await response.json();
42
+ if (body.error) throw new OAuth2Error(400, body.error, body.error_description);
43
+ return body;
29
44
  },
30
45
  async getUserInfo({ access_token }) {
31
46
  invariant(access_token, "access_token is required");
32
47
  invariant(this.userInfoUri, "userInfoUri is required");
33
48
  const headers = {
34
49
  Accept: "application/json",
35
- Authorization: `Bearer ${access_token}`
50
+ Authorization: `Bearer ${access_token}`,
51
+ "User-Agent": USER_AGENT
36
52
  };
37
53
  const response = await fetch(this.userInfoUri, { headers });
38
- if (!response.ok) throw new OAuth2Error(response.status, "invalid_request", "Failed to fetch user info");
54
+ if (!response.ok) {
55
+ const message = await describeFailure(response, "Failed to fetch user info");
56
+ throw new OAuth2Error(response.status, "invalid_request", message);
57
+ }
39
58
  const data = await response.json();
40
59
  const emailResponse = await fetch("https://api.github.com/user/emails", { headers });
41
- if (!emailResponse.ok) throw new OAuth2Error(emailResponse.status, "invalid_request", "Failed to fetch user email");
60
+ if (!emailResponse.ok) {
61
+ const message = await describeFailure(emailResponse, "Failed to fetch user email");
62
+ throw new OAuth2Error(emailResponse.status, "invalid_request", message);
63
+ }
42
64
  const emails = await emailResponse.json();
43
65
  data.email = emails.find((email) => email.primary)?.email || data.email;
44
66
  const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;
@@ -1 +1 @@
1
- {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GithubToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to fetch user info');\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n throw new OAuth2Error(\n emailResponse.status,\n 'invalid_request',\n 'Failed to fetch user email'\n );\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;AAKA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GACtF,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,2BAA2B;GAEvF,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IACjB,MAAM,IAAI,YACR,cAAc,QACd,mBACA,4BACF;GAEF,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAI,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAI,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/security",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",