nuxt-auth-utils 0.5.32 → 0.5.33

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.
Files changed (73) hide show
  1. package/README.md +55 -4
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -1
  4. package/dist/runtime/app/composables/session.js +8 -3
  5. package/dist/runtime/app/plugins/session.client.js +8 -4
  6. package/dist/runtime/server/api/session.delete.d.ts +3 -1
  7. package/dist/runtime/server/api/session.get.d.ts +1 -1
  8. package/dist/runtime/server/lib/atproto/bluesky.d.ts +1 -1
  9. package/dist/runtime/server/lib/oauth/apple.d.ts +1 -1
  10. package/dist/runtime/server/lib/oauth/apple.js +3 -0
  11. package/dist/runtime/server/lib/oauth/atlassian.d.ts +1 -1
  12. package/dist/runtime/server/lib/oauth/auth0.d.ts +1 -1
  13. package/dist/runtime/server/lib/oauth/authentik.d.ts +1 -1
  14. package/dist/runtime/server/lib/oauth/azureb2c.d.ts +1 -1
  15. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +1 -1
  16. package/dist/runtime/server/lib/oauth/box.d.ts +1 -1
  17. package/dist/runtime/server/lib/oauth/cloudflare.d.ts +86 -0
  18. package/dist/runtime/server/lib/oauth/cloudflare.js +72 -0
  19. package/dist/runtime/server/lib/oauth/cognito.d.ts +1 -1
  20. package/dist/runtime/server/lib/oauth/discord.d.ts +1 -1
  21. package/dist/runtime/server/lib/oauth/dropbox.d.ts +1 -1
  22. package/dist/runtime/server/lib/oauth/facebook.d.ts +14 -3
  23. package/dist/runtime/server/lib/oauth/facebook.js +15 -3
  24. package/dist/runtime/server/lib/oauth/gitea.d.ts +1 -1
  25. package/dist/runtime/server/lib/oauth/github.d.ts +1 -1
  26. package/dist/runtime/server/lib/oauth/gitlab.d.ts +1 -1
  27. package/dist/runtime/server/lib/oauth/google.d.ts +1 -1
  28. package/dist/runtime/server/lib/oauth/heroku.d.ts +1 -1
  29. package/dist/runtime/server/lib/oauth/hubspot.d.ts +1 -1
  30. package/dist/runtime/server/lib/oauth/instagram.d.ts +1 -1
  31. package/dist/runtime/server/lib/oauth/keycloak.d.ts +1 -1
  32. package/dist/runtime/server/lib/oauth/kick.d.ts +1 -1
  33. package/dist/runtime/server/lib/oauth/line.d.ts +1 -1
  34. package/dist/runtime/server/lib/oauth/linear.d.ts +1 -1
  35. package/dist/runtime/server/lib/oauth/linkedin.d.ts +1 -1
  36. package/dist/runtime/server/lib/oauth/livechat.d.ts +1 -1
  37. package/dist/runtime/server/lib/oauth/mastodon.d.ts +62 -0
  38. package/dist/runtime/server/lib/oauth/mastodon.js +178 -0
  39. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  40. package/dist/runtime/server/lib/oauth/oidc.d.ts +1 -1
  41. package/dist/runtime/server/lib/oauth/oidc.js +4 -3
  42. package/dist/runtime/server/lib/oauth/okta.d.ts +1 -1
  43. package/dist/runtime/server/lib/oauth/ory.d.ts +1 -1
  44. package/dist/runtime/server/lib/oauth/osu.d.ts +1 -1
  45. package/dist/runtime/server/lib/oauth/paypal.d.ts +1 -1
  46. package/dist/runtime/server/lib/oauth/polar.d.ts +1 -1
  47. package/dist/runtime/server/lib/oauth/riotgames.d.ts +1 -1
  48. package/dist/runtime/server/lib/oauth/roblox.d.ts +1 -1
  49. package/dist/runtime/server/lib/oauth/salesforce.d.ts +9 -4
  50. package/dist/runtime/server/lib/oauth/salesforce.js +8 -4
  51. package/dist/runtime/server/lib/oauth/seznam.d.ts +1 -1
  52. package/dist/runtime/server/lib/oauth/shopifyCustomer.d.ts +1 -1
  53. package/dist/runtime/server/lib/oauth/shopifyCustomer.js +1 -1
  54. package/dist/runtime/server/lib/oauth/slack.d.ts +1 -1
  55. package/dist/runtime/server/lib/oauth/spotify.d.ts +1 -1
  56. package/dist/runtime/server/lib/oauth/steam.d.ts +1 -1
  57. package/dist/runtime/server/lib/oauth/strava.d.ts +1 -1
  58. package/dist/runtime/server/lib/oauth/tiktok.d.ts +1 -1
  59. package/dist/runtime/server/lib/oauth/twitch.d.ts +1 -1
  60. package/dist/runtime/server/lib/oauth/vk.d.ts +1 -1
  61. package/dist/runtime/server/lib/oauth/wechat.d.ts +67 -0
  62. package/dist/runtime/server/lib/oauth/wechat.js +107 -0
  63. package/dist/runtime/server/lib/oauth/workos.d.ts +1 -1
  64. package/dist/runtime/server/lib/oauth/x.d.ts +1 -1
  65. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +1 -1
  66. package/dist/runtime/server/lib/oauth/yandex.d.ts +1 -1
  67. package/dist/runtime/server/lib/oauth/zitadel.d.ts +1 -1
  68. package/dist/runtime/server/lib/utils.d.ts +4 -3
  69. package/dist/runtime/server/lib/utils.js +3 -3
  70. package/dist/runtime/server/routes/atproto/client-metadata.json.get.d.ts +128 -1
  71. package/dist/runtime/server/utils/session.js +6 -1
  72. package/dist/runtime/types/oauth-config.d.ts +1 -1
  73. package/package.json +12 -16
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Add Authentication to Nuxt applications with secured & sealed cookies sessions.
9
9
 
10
- - [Release Notes](/CHANGELOG.md)
10
+ - [Release Notes](https://github.com/atinux/nuxt-auth-utils/releases)
11
11
  - [Demo with OAuth](https://github.com/atinux/atidone)
12
12
  - [Demo with Passkeys](https://github.com/atinux/todo-passkeys)
13
13
  <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/nuxt-auth-utils?file=playground%2Fapp.vue) -->
@@ -220,6 +220,7 @@ It can also be set using environment variables:
220
220
  - Battle.net
221
221
  - Bluesky (AT Protocol)
222
222
  - Box.com
223
+ - Cloudflare
223
224
  - Discord
224
225
  - Dropbox
225
226
  - Facebook
@@ -236,6 +237,7 @@ It can also be set using environment variables:
236
237
  - Linear
237
238
  - LinkedIn
238
239
  - LiveChat
240
+ - Mastodon
239
241
  - Microsoft
240
242
  - OIDC / OpenID Connect (Generic)
241
243
  - Okta
@@ -254,6 +256,7 @@ It can also be set using environment variables:
254
256
  - TikTok
255
257
  - Twitch
256
258
  - VK
259
+ - WeChat
257
260
  - WorkOS
258
261
  - X (Twitter)
259
262
  - XSUAA
@@ -291,6 +294,34 @@ Make sure to set the callback URL in your OAuth app settings as `<your-domain>/a
291
294
 
292
295
  If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the `NUXT_OAUTH_<PROVIDER>_REDIRECT_URL` env variable to overwrite the default one.
293
296
 
297
+ #### Mastodon
298
+
299
+ Mastodon does not have a single shared OAuth server: every account lives on its own instance (e.g. `mastodon.social`, `h4.io`), and there is no portable identity across instances the way there is with AT Protocol/Bluesky. Because of this, the provider needs to know which instance to talk to before it can start the flow, and it registers an application against that instance on first use via `POST /api/v1/apps` (Mastodon's own API, not the RFC 7591 Dynamic Client Registration spec it superficially resembles).
300
+
301
+ ```ts
302
+ // server/routes/auth/mastodon.get.ts
303
+ export default defineOAuthMastodonEventHandler({
304
+ async onSuccess(event, { user }) {
305
+ await setUserSession(event, {
306
+ user: {
307
+ mastodon: user.acct,
308
+ },
309
+ })
310
+ return sendRedirect(event, '/')
311
+ },
312
+ })
313
+ ```
314
+
315
+ The instance can be provided either as a static `instance` config/`NUXT_OAUTH_MASTODON_INSTANCE` env variable, or per-request as an `instance` query parameter, e.g. `/auth/mastodon?instance=mastodon.social`:
316
+
317
+ ```ts
318
+ navigateTo({ path: '/auth/mastodon', query: { instance: 'mastodon.social' } }, { external: true })
319
+ ```
320
+
321
+ A full handle (`@user@mastodon.social`) is also accepted and only the instance part is used, since that's all that is required to start the OAuth flow.
322
+
323
+ Registered apps are cached in memory per instance and redirect URL for the lifetime of the server process, so it does not re-register on every login. The callback doesn't depend on this cache, so after a restart or on another worker, the next login only registers a new application.
324
+
294
325
  ### Password Hashing
295
326
 
296
327
  Nuxt Auth Utils provides password hashing utilities like `hashPassword` and `verifyPassword` to hash and verify passwords by using [scrypt](https://en.wikipedia.org/wiki/Scrypt) as it is supported in many JS runtime.
@@ -778,6 +809,27 @@ await setUserSession(event, { ... } , {
778
809
 
779
810
  Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.
780
811
 
812
+ ## Shared session between microservices and subdomains
813
+
814
+ To share a session between multiple applications, use the same `NUXT_SESSION_PASSWORD` and session `name` in each of them.
815
+
816
+ If the applications run on different subdomains, also set the session cookie `domain` to the parent domain in each `nuxt.config.ts`:
817
+
818
+ ```ts
819
+ export default defineNuxtConfig({
820
+ modules: ['nuxt-auth-utils'],
821
+ runtimeConfig: {
822
+ session: {
823
+ cookie: {
824
+ domain: '.example.com'
825
+ }
826
+ }
827
+ }
828
+ })
829
+ ```
830
+
831
+ The browser then sends the session cookie to every subdomain of `example.com`, and each application can decrypt it.
832
+
781
833
  ## More
782
834
 
783
835
  - [nuxt-authorization](https://github.com/barbapapazes/nuxt-authorization): Authorization module for managing permissions inside a Nuxt app, compatible with `nuxt-auth-utils`
@@ -803,11 +855,10 @@ pnpm run lint
803
855
  # Run Vitest
804
856
  pnpm run test
805
857
  pnpm run test:watch
806
-
807
- # Release new version
808
- pnpm run release
809
858
  ```
810
859
 
860
+ Releases are automated with [uppt](https://github.com/danielroe/uppt): every push to `main` opens or updates a draft release PR from conventional commits. Merging it tags the version, creates the GitHub release and stages the npm publish, which is then approved on npmjs.com.
861
+
811
862
  <!-- Badges -->
812
863
  [npm-version-src]: https://img.shields.io/npm/v/nuxt-auth-utils/latest.svg?style=flat&colorA=020420&colorB=00DC82
813
864
  [npm-version-href]: https://npmjs.com/package/nuxt-auth-utils
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.32",
4
+ "version": "0.5.33",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -191,6 +191,11 @@ const module$1 = defineNuxtModule({
191
191
  clientSecret: "",
192
192
  redirectURL: ""
193
193
  });
194
+ runtimeConfig.oauth.cloudflare = defu(runtimeConfig.oauth.cloudflare, {
195
+ clientId: "",
196
+ clientSecret: "",
197
+ redirectURL: ""
198
+ });
194
199
  for (const provider of atprotoProviders) {
195
200
  runtimeConfig.oauth[provider] = defu(runtimeConfig.oauth[provider], atprotoProviderDefaultClientMetadata);
196
201
  }
@@ -235,6 +240,11 @@ const module$1 = defineNuxtModule({
235
240
  clientSecret: "",
236
241
  redirectURL: ""
237
242
  });
243
+ runtimeConfig.oauth.mastodon = defu(runtimeConfig.oauth.mastodon, {
244
+ instance: "",
245
+ clientName: "",
246
+ redirectURL: ""
247
+ });
238
248
  runtimeConfig.oauth.cognito = defu(runtimeConfig.oauth.cognito, {
239
249
  clientId: "",
240
250
  clientSecret: "",
@@ -245,7 +255,8 @@ const module$1 = defineNuxtModule({
245
255
  runtimeConfig.oauth.facebook = defu(runtimeConfig.oauth.facebook, {
246
256
  clientId: "",
247
257
  clientSecret: "",
248
- redirectURL: ""
258
+ redirectURL: "",
259
+ apiVersion: "v25.0"
249
260
  });
250
261
  runtimeConfig.oauth.instagram = defu(runtimeConfig.oauth.instagram, {
251
262
  clientId: "",
@@ -329,6 +340,12 @@ const module$1 = defineNuxtModule({
329
340
  clientSecret: "",
330
341
  redirectURL: ""
331
342
  });
343
+ runtimeConfig.oauth.wechat = defu(runtimeConfig.oauth.wechat, {
344
+ appId: "",
345
+ appSecret: "",
346
+ redirectURL: "",
347
+ scope: []
348
+ });
332
349
  runtimeConfig.oauth.atlassian = defu(runtimeConfig.oauth.atlassian, {
333
350
  clientId: "",
334
351
  clientSecret: "",
@@ -355,6 +372,7 @@ const module$1 = defineNuxtModule({
355
372
  clientSecret: "",
356
373
  redirectURL: "",
357
374
  baseURL: "",
375
+ userURL: "",
358
376
  scope: ""
359
377
  });
360
378
  runtimeConfig.oauth.slack = defu(runtimeConfig.oauth.slack, {
@@ -4,8 +4,9 @@ export function useUserSession() {
4
4
  const serverEvent = import.meta.server ? useRequestEvent() : null;
5
5
  const sessionState = useState("nuxt-session", () => null);
6
6
  const authReadyState = useState("nuxt-auth-ready", () => false);
7
+ const requestFetch = useRequestFetch();
7
8
  const clear = async () => {
8
- await useRequestFetch()("/api/_auth/session", {
9
+ await requestFetch("/api/_auth/session", {
9
10
  method: "DELETE",
10
11
  onResponse({ response: { headers } }) {
11
12
  if (import.meta.server && serverEvent) {
@@ -18,7 +19,7 @@ export function useUserSession() {
18
19
  sessionState.value = null;
19
20
  };
20
21
  const fetch = async () => {
21
- sessionState.value = await useRequestFetch()("/api/_auth/session", {
22
+ sessionState.value = await requestFetch("/api/_auth/session", {
22
23
  headers: {
23
24
  accept: "application/json"
24
25
  },
@@ -35,7 +36,11 @@ export function useUserSession() {
35
36
  }
36
37
  };
37
38
  const openInPopup = (route, size = {}) => {
38
- localStorage.setItem("temp-nuxt-auth-utils-popup", "true");
39
+ try {
40
+ localStorage.setItem("temp-nuxt-auth-utils-popup", "true");
41
+ } catch (error) {
42
+ console.error("[nuxt-auth-utils] Could not use localStorage during popup flow initialization.", error);
43
+ }
39
44
  const width = size.width ?? 960;
40
45
  const height = size.height ?? 600;
41
46
  const top = (window.top?.outerHeight ?? 0) / 2 + (window.top?.screenY ?? 0) - height / 2;
@@ -7,9 +7,13 @@ export default defineNuxtPlugin(async (nuxtApp) => {
7
7
  await useUserSession().fetch();
8
8
  });
9
9
  }
10
- if (localStorage.getItem("temp-nuxt-auth-utils-popup")) {
11
- localStorage.removeItem("temp-nuxt-auth-utils-popup");
12
- const error = useError();
13
- if (!error.value) window.close();
10
+ try {
11
+ if (localStorage.getItem("temp-nuxt-auth-utils-popup")) {
12
+ localStorage.removeItem("temp-nuxt-auth-utils-popup");
13
+ const error = useError();
14
+ if (!error.value) window.close();
15
+ }
16
+ } catch (error) {
17
+ console.error("[nuxt-auth-utils] Could not use localStorage during popup flow last step. Popup won't be closed automatically.", error);
14
18
  }
15
19
  });
@@ -1,2 +1,4 @@
1
- declare const _default: any;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ loggedOut: boolean;
3
+ }>>;
2
4
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<UserSession>>;
2
2
  export default _default;
@@ -22,7 +22,7 @@ type BlueSkyTokens = NodeSavedSession['tokenSet'];
22
22
  export declare function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBlueskyConfig, {
23
23
  user: BlueSkyUser;
24
24
  tokens: BlueSkyTokens;
25
- }>): any;
25
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
26
26
  export declare class StateStore implements NodeSavedStateStore {
27
27
  private event;
28
28
  private readonly stateKey;
@@ -74,4 +74,4 @@ export declare function defineOAuthAppleEventHandler({ config, onSuccess, onErro
74
74
  user: OAuthAppleUser;
75
75
  payload: OAuthAppleTokens;
76
76
  tokens: any;
77
- }>): any;
77
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -71,6 +71,9 @@ export function defineOAuthAppleEventHandler({
71
71
  redirect_uri: config.redirectURL
72
72
  }
73
73
  });
74
+ if (accessTokenResult.error) {
75
+ return handleAccessTokenErrorResponse(event, "apple", accessTokenResult, onError);
76
+ }
74
77
  const payload = await verifyJwt(accessTokenResult.id_token, {
75
78
  publicJwkUrl: "https://appleid.apple.com/auth/keys",
76
79
  audience: config.clientId,
@@ -91,5 +91,5 @@ export interface OAuthAtlassianConfig {
91
91
  export declare function defineOAuthAtlassianEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthAtlassianConfig, {
92
92
  user: AtlassianUser;
93
93
  tokens: AtlassianTokens;
94
- }>): any;
94
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
95
95
  export {};
@@ -57,4 +57,4 @@ export interface OAuthAuth0Config {
57
57
  */
58
58
  redirectURL?: string;
59
59
  }
60
- export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): any;
60
+ export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -27,4 +27,4 @@ export interface OAuthAuthentikConfig {
27
27
  */
28
28
  scope?: string[];
29
29
  }
30
- export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): any;
30
+ export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -51,4 +51,4 @@ export interface OAuthAzureB2CConfig {
51
51
  */
52
52
  redirectURL?: string;
53
53
  }
54
- export declare function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAzureB2CConfig>): any;
54
+ export declare function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAzureB2CConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -45,4 +45,4 @@ export interface OAuthBattledotnetConfig {
45
45
  */
46
46
  redirectURL?: string;
47
47
  }
48
- export declare function defineOAuthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig>): any;
48
+ export declare function defineOAuthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -91,5 +91,5 @@ interface OAuthBoxTokens {
91
91
  export declare function defineOAuthBoxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBoxConfig, {
92
92
  user: OAuthBoxUser;
93
93
  tokens: OAuthBoxTokens;
94
- }>): any;
94
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
95
95
  export {};
@@ -0,0 +1,86 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ /**
3
+ * Cloudflare OAuth provider — Authorization Code flow (the only flow Cloudflare supports).
4
+ *
5
+ * References:
6
+ * - Endpoints: https://developers.cloudflare.com/fundamentals/oauth/integrate-with-cloudflare/
7
+ * - Flow/PKCE: https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/#supported-oauth-flows
8
+ * - Scopes: https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/#select-scopes
9
+ *
10
+ * Scopes are dot-notation API-permission names (e.g. `workers-platform.read`) fetched from
11
+ * `GET /client/v4/oauth/scopes` — `openid` is NOT a Cloudflare scope. PKCE (S256) is required
12
+ * for public clients (auth method `none`) and optional for confidential clients (client secret).
13
+ */
14
+ export interface OAuthCloudflareConfig {
15
+ /**
16
+ * Cloudflare OAuth Client ID
17
+ * @default process.env.NUXT_OAUTH_CLOUDFLARE_CLIENT_ID
18
+ */
19
+ clientId?: string;
20
+ /**
21
+ * Cloudflare OAuth Client Secret. Required for confidential clients
22
+ * (`client_secret_post`/`client_secret_basic`); omit for public PKCE-only
23
+ * clients (`token_endpoint_auth_method=none`).
24
+ * @default process.env.NUXT_OAUTH_CLOUDFLARE_CLIENT_SECRET
25
+ */
26
+ clientSecret?: string;
27
+ /**
28
+ * Cloudflare OAuth scopes — dot-notation API token permission IDs from
29
+ * `GET /client/v4/oauth/scopes`, matching the scopes the client was registered with.
30
+ * Cloudflare requires at least one; there is no default (`openid` is not a Cloudflare scope).
31
+ * @see https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/#select-scopes
32
+ * @example ['workers-platform.read']
33
+ */
34
+ scope?: string[];
35
+ /**
36
+ * Cloudflare OAuth Authorization URL
37
+ * @default 'https://dash.cloudflare.com/oauth2/auth'
38
+ */
39
+ authorizationURL?: string;
40
+ /**
41
+ * Cloudflare OAuth Token URL
42
+ * @default 'https://dash.cloudflare.com/oauth2/token'
43
+ */
44
+ tokenURL?: string;
45
+ /**
46
+ * Cloudflare OIDC userinfo URL. Returns standard claims (`sub`, `iss`, `aud`, …);
47
+ * use `GET /client/v4/user` for email/name.
48
+ * @default 'https://dash.cloudflare.com/oauth2/userinfo'
49
+ */
50
+ userURL?: string;
51
+ /**
52
+ * Extra authorization parameters to provide to the authorization URL
53
+ */
54
+ authorizationParams?: Record<string, string>;
55
+ /**
56
+ * Redirect URL to allow overriding for situations like prod failing to determine public hostname
57
+ * @default process.env.NUXT_OAUTH_CLOUDFLARE_REDIRECT_URL or current URL
58
+ */
59
+ redirectURL?: string;
60
+ }
61
+ /**
62
+ * Claims returned by Cloudflare's `/oauth2/userinfo` endpoint. `sub` is the stable
63
+ * Cloudflare user ID; the rest are standard OIDC token claims. There is no email/name
64
+ * here — for richer profile data call `GET /client/v4/user` with the access token.
65
+ */
66
+ export interface CloudflareUser {
67
+ /** Stable Cloudflare user ID. */
68
+ sub: string;
69
+ aud?: string[];
70
+ iss?: string;
71
+ iat?: number;
72
+ auth_time?: number;
73
+ rat?: number;
74
+ }
75
+ export interface CloudflareTokens {
76
+ access_token: string;
77
+ token_type: string;
78
+ expires_in: number;
79
+ /** Present when scope includes `offline_access` */
80
+ refresh_token?: string;
81
+ scope: string;
82
+ }
83
+ export declare function defineOAuthCloudflareEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCloudflareConfig, {
84
+ user: CloudflareUser;
85
+ tokens: CloudflareTokens;
86
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -0,0 +1,72 @@
1
+ import { eventHandler, getQuery, sendRedirect, createError } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function defineOAuthCloudflareEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.cloudflare, {
9
+ authorizationURL: "https://dash.cloudflare.com/oauth2/auth",
10
+ tokenURL: "https://dash.cloudflare.com/oauth2/token",
11
+ userURL: "https://dash.cloudflare.com/oauth2/userinfo",
12
+ authorizationParams: {}
13
+ });
14
+ const query = getQuery(event);
15
+ if (query.error) {
16
+ const error = createError({
17
+ statusCode: 401,
18
+ message: `Cloudflare login failed: ${query.error_description || query.error}`,
19
+ data: query
20
+ });
21
+ if (!onError) throw error;
22
+ return onError(event, error);
23
+ }
24
+ if (!config.clientId) {
25
+ return handleMissingConfiguration(event, "cloudflare", ["clientId"], onError);
26
+ }
27
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
28
+ const state = await handleState(event);
29
+ const pkce = await handlePkceVerifier(event);
30
+ if (!query.code) {
31
+ return sendRedirect(
32
+ event,
33
+ withQuery(config.authorizationURL, {
34
+ response_type: "code",
35
+ client_id: config.clientId,
36
+ redirect_uri: redirectURL,
37
+ scope: config.scope?.length ? config.scope.join(" ") : void 0,
38
+ ...config.authorizationParams,
39
+ code_challenge: pkce.code_challenge,
40
+ code_challenge_method: pkce.code_challenge_method,
41
+ state
42
+ })
43
+ );
44
+ }
45
+ if (!state || query.state !== state) {
46
+ return handleInvalidState(event, "cloudflare", onError);
47
+ }
48
+ const tokens = await requestAccessToken(config.tokenURL, {
49
+ body: {
50
+ grant_type: "authorization_code",
51
+ client_id: config.clientId,
52
+ // Public (PKCE-only) clients authenticate with `none` and send no secret.
53
+ ...config.clientSecret ? { client_secret: config.clientSecret } : {},
54
+ redirect_uri: redirectURL,
55
+ code: query.code,
56
+ code_verifier: pkce.code_verifier
57
+ }
58
+ });
59
+ if (tokens.error) {
60
+ return handleAccessTokenErrorResponse(event, "cloudflare", tokens, onError);
61
+ }
62
+ const user = await $fetch(config.userURL, {
63
+ headers: {
64
+ Authorization: `Bearer ${tokens.access_token}`
65
+ }
66
+ });
67
+ return onSuccess(event, {
68
+ user,
69
+ tokens
70
+ });
71
+ });
72
+ }
@@ -36,4 +36,4 @@ export interface OAuthCognitoConfig {
36
36
  */
37
37
  redirectURL?: string;
38
38
  }
39
- export declare function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): any;
39
+ export declare function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -50,4 +50,4 @@ export interface OAuthDiscordConfig {
50
50
  */
51
51
  redirectURL?: string;
52
52
  }
53
- export declare function defineOAuthDiscordEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDiscordConfig>): any;
53
+ export declare function defineOAuthDiscordEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDiscordConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -44,4 +44,4 @@ export interface OAuthDropboxConfig {
44
44
  */
45
45
  redirectURL?: string;
46
46
  }
47
- export declare function defineOAuthDropboxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDropboxConfig>): any;
47
+ export declare function defineOAuthDropboxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDropboxConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -10,6 +10,12 @@ export interface OAuthFacebookConfig {
10
10
  * @default process.env.NUXT_OAUTH_FACEBOOK_CLIENT_SECRET
11
11
  */
12
12
  clientSecret?: string;
13
+ /**
14
+ * Facebook Graph API version used for default OAuth and /me URLs
15
+ * @default 'v25.0'
16
+ * @see https://developers.facebook.com/docs/graph-api/changelog/versions/
17
+ */
18
+ apiVersion?: string;
13
19
  /**
14
20
  * Facebook OAuth Scope
15
21
  * @default []
@@ -26,14 +32,19 @@ export interface OAuthFacebookConfig {
26
32
  fields?: string[];
27
33
  /**
28
34
  * Facebook OAuth Authorization URL
29
- * @default 'https://www.facebook.com/v19.0/dialog/oauth'
35
+ * @default 'https://www.facebook.com/${apiVersion}/dialog/oauth'
30
36
  */
31
37
  authorizationURL?: string;
32
38
  /**
33
39
  * Facebook OAuth Token URL
34
- * @default 'https://graph.facebook.com/v19.0/oauth/access_token'
40
+ * @default 'https://graph.facebook.com/${apiVersion}/oauth/access_token'
35
41
  */
36
42
  tokenURL?: string;
43
+ /**
44
+ * Facebook Graph API user profile URL
45
+ * @default 'https://graph.facebook.com/${apiVersion}/me'
46
+ */
47
+ userURL?: string;
37
48
  /**
38
49
  * Extra authorization parameters to provide to the authorization URL
39
50
  * @see https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/
@@ -45,4 +56,4 @@ export interface OAuthFacebookConfig {
45
56
  */
46
57
  redirectURL?: string;
47
58
  }
48
- export declare function defineOAuthFacebookEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthFacebookConfig>): any;
59
+ export declare function defineOAuthFacebookEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthFacebookConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -1,6 +1,7 @@
1
1
  import { eventHandler, getQuery, sendRedirect, createError } from "h3";
2
2
  import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
+ import { subtle } from "uncrypto";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
6
  import { useRuntimeConfig } from "#imports";
6
7
  export function defineOAuthFacebookEventHandler({
@@ -9,9 +10,12 @@ export function defineOAuthFacebookEventHandler({
9
10
  onError
10
11
  }) {
11
12
  return eventHandler(async (event) => {
13
+ const apiVersion = config?.apiVersion || useRuntimeConfig(event).oauth?.facebook?.apiVersion || "v25.0";
12
14
  config = defu(config, useRuntimeConfig(event).oauth?.facebook, {
13
- authorizationURL: "https://www.facebook.com/v19.0/dialog/oauth",
14
- tokenURL: "https://graph.facebook.com/v19.0/oauth/access_token",
15
+ apiVersion,
16
+ authorizationURL: `https://www.facebook.com/${apiVersion}/dialog/oauth`,
17
+ tokenURL: `https://graph.facebook.com/${apiVersion}/oauth/access_token`,
18
+ userURL: `https://graph.facebook.com/${apiVersion}/me`,
15
19
  authorizationParams: {}
16
20
  });
17
21
  const query = getQuery(event);
@@ -60,8 +64,16 @@ export function defineOAuthFacebookEventHandler({
60
64
  const accessToken = tokens.access_token;
61
65
  config.fields = [...new Set(config.fields || ["id", "name"])];
62
66
  const fields = config.fields.join(",");
67
+ const encoder = new TextEncoder();
68
+ const key = await subtle.importKey("raw", encoder.encode(config.clientSecret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
69
+ const signature = await subtle.sign("HMAC", key, encoder.encode(accessToken));
70
+ const appsecretProof = Array.from(new Uint8Array(signature), (b) => b.toString(16).padStart(2, "0")).join("");
63
71
  const user = await $fetch(
64
- `https://graph.facebook.com/v19.0/me?fields=${fields}&access_token=${accessToken}`
72
+ withQuery(config.userURL, {
73
+ fields,
74
+ access_token: accessToken,
75
+ appsecret_proof: appsecretProof
76
+ })
65
77
  );
66
78
  if (!user) {
67
79
  const error = createError({
@@ -48,4 +48,4 @@ export interface OAuthGiteaConfig {
48
48
  */
49
49
  baseURL?: string;
50
50
  }
51
- export declare function defineOAuthGiteaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGiteaConfig>): any;
51
+ export declare function defineOAuthGiteaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGiteaConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -93,5 +93,5 @@ interface GitHubTokens {
93
93
  export declare function defineOAuthGitHubEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthGitHubConfig, {
94
94
  user: GitHubUser;
95
95
  tokens: GitHubTokens;
96
- }>): any;
96
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
97
97
  export {};
@@ -49,4 +49,4 @@ export interface OAuthGitLabConfig {
49
49
  */
50
50
  baseURL?: string;
51
51
  }
52
- export declare function defineOAuthGitLabEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGitLabConfig>): any;
52
+ export declare function defineOAuthGitLabEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGitLabConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -44,4 +44,4 @@ export interface OAuthGoogleConfig {
44
44
  */
45
45
  redirectURL?: string;
46
46
  }
47
- export declare function defineOAuthGoogleEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGoogleConfig>): any;
47
+ export declare function defineOAuthGoogleEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGoogleConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -38,4 +38,4 @@ export interface OAuthHerokuConfig {
38
38
  */
39
39
  redirectURL?: string;
40
40
  }
41
- export declare function defineOAuthHerokuEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthHerokuConfig>): any;
41
+ export declare function defineOAuthHerokuEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthHerokuConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -23,4 +23,4 @@ export interface OAuthHubspotConfig {
23
23
  */
24
24
  scope?: string[];
25
25
  }
26
- export declare function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthHubspotConfig>): any;
26
+ export declare function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthHubspotConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -45,4 +45,4 @@ export interface OAuthInstagramConfig {
45
45
  */
46
46
  redirectURL?: string;
47
47
  }
48
- export declare function defineOAuthInstagramEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthInstagramConfig>): any;
48
+ export declare function defineOAuthInstagramEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthInstagramConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -45,4 +45,4 @@ export interface OAuthKeycloakConfig {
45
45
  */
46
46
  redirectURL?: string;
47
47
  }
48
- export declare function defineOAuthKeycloakEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthKeycloakConfig>): any;
48
+ export declare function defineOAuthKeycloakEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthKeycloakConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
@@ -51,5 +51,5 @@ interface KickTokens {
51
51
  export declare function defineOAuthKickEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthKickConfig, {
52
52
  user: KickUser;
53
53
  tokens: KickTokens;
54
- }>): any;
54
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
55
55
  export {};