nuxt-auth-utils 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,14 +16,14 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
16
16
  ## Features
17
17
 
18
18
  - [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
19
- - [20+ OAuth Providers](#supported-oauth-providers)
19
+ - [30+ OAuth Providers](#supported-oauth-providers)
20
20
  - [Password Hashing](#password-hashing)
21
21
  - [WebAuthn (passkey)](#webauthn-passkey)
22
22
  - [`useUserSession()` Vue composable](#vue-composable)
23
23
  - [Tree-shakable server utils](#server-utils)
24
24
  - [`<AuthState>` component](#authstate-component)
25
25
  - [Extendable with hooks](#extend-session)
26
- - [WebSocket support](#websockets-support)
26
+ - [WebSocket support](#websocket-support)
27
27
 
28
28
  It has few dependencies (only from [UnJS](https://github.com/unjs)), run on multiple JS environments (Node, Deno, Workers) and is fully typed with TypeScript.
29
29
 
@@ -62,7 +62,7 @@ Nuxt Auth Utils automatically adds some plugins to fetch the current user sessio
62
62
 
63
63
  ```vue
64
64
  <script setup>
65
- const { loggedIn, user, session, fetch, clear } = useUserSession()
65
+ const { loggedIn, user, session, fetch, clear, openInPopup } = useUserSession()
66
66
  </script>
67
67
 
68
68
  <template>
@@ -74,6 +74,8 @@ const { loggedIn, user, session, fetch, clear } = useUserSession()
74
74
  <div v-else>
75
75
  <h1>Not logged in</h1>
76
76
  <a href="/auth/github">Login with GitHub</a>
77
+ <!-- or open the OAuth route in a popup -->
78
+ <button @click="openInPopup('/auth/github')">Login with GitHub</button>
77
79
  </div>
78
80
  </template>
79
81
  ```
@@ -106,6 +108,10 @@ interface UserSessionComposable {
106
108
  * Clear the user session and remove the session cookie.
107
109
  */
108
110
  clear: () => Promise<void>
111
+ /**
112
+ * Open the OAuth route in a popup that auto-closes when successful.
113
+ */
114
+ openInPopup: (route: string, size?: { width?: number, height?: number }) => void
109
115
  }
110
116
  ```
111
117
 
@@ -205,6 +211,7 @@ It can also be set using environment variables:
205
211
 
206
212
  #### Supported OAuth Providers
207
213
 
214
+ - Apple
208
215
  - Atlassian
209
216
  - Auth0
210
217
  - Authentik
@@ -215,6 +222,7 @@ It can also be set using environment variables:
215
222
  - Facebook
216
223
  - GitHub
217
224
  - GitLab
225
+ - Gitea
218
226
  - Google
219
227
  - Hubspot
220
228
  - Instagram
@@ -597,7 +605,7 @@ You can use the `placeholder` slot to show a placeholder on server-side and whil
597
605
 
598
606
  If you are caching your routes with `routeRules`, please make sure to use [Nitro](https://github.com/unjs/nitro) >= `2.9.7` to support the client-side fetching of the user session.
599
607
 
600
- ## WebSockets Support
608
+ ## WebSocket Support
601
609
 
602
610
  Nuxt Auth Utils is compatible with [Nitro WebSockets](https://nitro.build/guide/websocket).
603
611
 
@@ -624,8 +632,8 @@ export default defineWebSocketHandler({
624
632
  },
625
633
  async open(peer) {
626
634
  const { user } = await requireUserSession(peer)
627
- const username = Object.values(user).filter(Boolean).join(' ')
628
- peer.send(`Hello, ${username}!`)
635
+
636
+ peer.send(`Hello, ${user.name}!`)
629
637
  },
630
638
  message(peer, message) {
631
639
  peer.send(`Echo: ${message}`)
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.9",
4
+ "version": "0.5.11",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -97,6 +97,12 @@ const module = defineNuxtModule({
97
97
  authenticate: {}
98
98
  });
99
99
  runtimeConfig.oauth = defu(runtimeConfig.oauth, {});
100
+ runtimeConfig.oauth.gitea = defu(runtimeConfig.oauth.gitea, {
101
+ clientId: "",
102
+ clientSecret: "",
103
+ redirectURL: "",
104
+ baseURL: ""
105
+ });
100
106
  runtimeConfig.oauth.github = defu(runtimeConfig.oauth.github, {
101
107
  clientId: "",
102
108
  clientSecret: "",
@@ -274,6 +280,13 @@ const module = defineNuxtModule({
274
280
  clientSecret: "",
275
281
  redirectURL: ""
276
282
  });
283
+ runtimeConfig.oauth.apple = defu(runtimeConfig.oauth.apple, {
284
+ teamId: "",
285
+ keyId: "",
286
+ privateKey: "",
287
+ redirectURL: "",
288
+ clientId: ""
289
+ });
277
290
  }
278
291
  });
279
292
 
@@ -28,12 +28,32 @@ export function useUserSession() {
28
28
  authReadyState.value = true;
29
29
  }
30
30
  };
31
+ const popupListener = (e) => {
32
+ if (e.key === "temp-nuxt-auth-utils-popup") {
33
+ fetch();
34
+ window.removeEventListener("storage", popupListener);
35
+ }
36
+ };
37
+ const openInPopup = (route, size = {}) => {
38
+ localStorage.setItem("temp-nuxt-auth-utils-popup", "true");
39
+ const width = size.width ?? 960;
40
+ const height = size.height ?? 600;
41
+ const top = (window.top?.outerHeight ?? 0) / 2 + (window.top?.screenY ?? 0) - height / 2;
42
+ const left = (window.top?.outerWidth ?? 0) / 2 + (window.top?.screenX ?? 0) - width / 2;
43
+ window.open(
44
+ route,
45
+ "nuxt-auth-utils-popup",
46
+ `width=${width}, height=${height}, top=${top}, left=${left}, toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no`
47
+ );
48
+ window.addEventListener("storage", popupListener);
49
+ };
31
50
  return {
32
51
  ready: computed(() => authReadyState.value),
33
52
  loggedIn: computed(() => Boolean(sessionState.value.user)),
34
53
  user: computed(() => sessionState.value.user || null),
35
54
  session: sessionState,
36
55
  fetch,
56
+ openInPopup,
37
57
  clear
38
58
  };
39
59
  }
@@ -7,4 +7,9 @@ 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();
14
+ }
10
15
  });
@@ -0,0 +1,73 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthAppleConfig {
3
+ /**
4
+ * Apple OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_APPLE_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Apple OAuth team ID
10
+ * @default process.env.NUXT_OAUTH_APPLE_TEAM_ID
11
+ */
12
+ teamId?: string;
13
+ /**
14
+ * Apple OAuth key identifier
15
+ * @default process.env.NUXT_OAUTH_APPLE_KEY_ID
16
+ */
17
+ keyId?: string;
18
+ /**
19
+ * Apple OAuth Private Key. Linebreaks must be replaced with \n
20
+ * @example '-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM...\n-----END PRIVATE KEY-----'
21
+ * @default process.env.NUXT_OAUTH_APPLE_PRIVATE_KEY
22
+ */
23
+ privateKey?: string;
24
+ /**
25
+ * Apple OAuth Scope. Apple wants this to be a string separated by spaces, but for consistency with other providers, we also allow an array of strings.
26
+ * @default ''
27
+ * @see https://developer.apple.com/documentation/sign_in_with_apple/clientconfigi/3230955-scope
28
+ * @example 'name email'
29
+ */
30
+ scope?: string | string[];
31
+ /**
32
+ * Apple OAuth Authorization URL
33
+ * @default 'https://appleid.apple.com/auth/authorize'
34
+ */
35
+ authorizationURL?: string;
36
+ /**
37
+ * Extra authorization parameters to provide to the authorization URL
38
+ * @see https://developer.apple.com/documentation/sign_in_with_apple/clientconfigi/3230955-scope
39
+ * @example { usePop: true }
40
+ */
41
+ authorizationParams?: Record<string, string | boolean>;
42
+ /**
43
+ * Apple OAuth Token URL
44
+ * @default 'https://appleid.apple.com/auth/token'
45
+ */
46
+ tokenURL?: string;
47
+ /**
48
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
49
+ * @default process.env.NUXT_OAUTH_APPLE_REDIRECT_URL or current URL
50
+ */
51
+ redirectURL?: string;
52
+ }
53
+ export interface OAuthAppleTokens {
54
+ iss: string;
55
+ aud: string;
56
+ exp: number;
57
+ iat: number;
58
+ sub: string;
59
+ at_hash: string;
60
+ email: string;
61
+ email_verified: boolean;
62
+ is_private_email: boolean;
63
+ auth_time: number;
64
+ nonce_supported: boolean;
65
+ }
66
+ export interface OAuthAppleUser {
67
+ name?: {
68
+ firstName?: string;
69
+ lastName?: string;
70
+ };
71
+ email?: string;
72
+ }
73
+ export declare function defineOAuthAppleEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthAppleConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,77 @@
1
+ import { eventHandler, getRequestHeader, readBody, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, signJwt, verifyJwt } from "../utils.js";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function defineOAuthAppleEventHandler({
7
+ config,
8
+ onSuccess,
9
+ onError
10
+ }) {
11
+ return eventHandler(async (event) => {
12
+ config = defu(config, useRuntimeConfig(event).oauth?.apple, {
13
+ authorizationURL: config?.authorizationURL || "https://appleid.apple.com/auth/authorize",
14
+ authorizationParams: {}
15
+ });
16
+ if (!config.teamId || !config.keyId || !config.privateKey || !config.clientId) {
17
+ return handleMissingConfiguration(event, "apple", ["teamId", "keyId", "privateKey", "clientId"], onError);
18
+ }
19
+ const isPost = getRequestHeader(event, "content-type") === "application/x-www-form-urlencoded";
20
+ let code;
21
+ let user;
22
+ if (isPost) {
23
+ ({ code, user } = await readBody(event));
24
+ }
25
+ if (!isPost || !code) {
26
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
27
+ config.scope = Array.isArray(config.scope) ? config.scope.join(" ") : config.scope || "name email";
28
+ return sendRedirect(
29
+ event,
30
+ withQuery(config.authorizationURL, {
31
+ response_type: "code",
32
+ response_mode: "form_post",
33
+ client_id: config.clientId,
34
+ redirect_uri: redirectURL,
35
+ scope: config.scope,
36
+ ...config.authorizationParams
37
+ })
38
+ );
39
+ }
40
+ try {
41
+ const secret = await signJwt(
42
+ {
43
+ iss: config.teamId,
44
+ aud: "https://appleid.apple.com",
45
+ sub: config.clientId
46
+ },
47
+ {
48
+ privateKey: config.privateKey,
49
+ keyId: config.keyId,
50
+ teamId: config.teamId,
51
+ clientId: config.clientId,
52
+ expiresIn: "5m"
53
+ }
54
+ );
55
+ const accessTokenResult = await requestAccessToken(config.tokenURL || "https://appleid.apple.com/auth/token", {
56
+ params: {
57
+ client_id: config.clientId,
58
+ client_secret: secret,
59
+ code,
60
+ grant_type: "authorization_code",
61
+ redirect_uri: config.redirectURL
62
+ }
63
+ });
64
+ const tokens = await verifyJwt(accessTokenResult.id_token, {
65
+ publicJwkUrl: "https://appleid.apple.com/auth/keys",
66
+ audience: config.clientId,
67
+ issuer: "https://appleid.apple.com"
68
+ });
69
+ if (!tokens) {
70
+ return handleAccessTokenErrorResponse(event, "apple", tokens, onError);
71
+ }
72
+ return onSuccess(event, { user, tokens });
73
+ } catch (error) {
74
+ return handleAccessTokenErrorResponse(event, "apple", error, onError);
75
+ }
76
+ });
77
+ }
@@ -0,0 +1,51 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthGiteaConfig {
3
+ /**
4
+ * Gitea OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_GITEA_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Gitea OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_GITEA_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Gitea OAuth Scope
15
+ * @default ['read:user']
16
+ * @see https://docs.gitea.io/en-us/oauth2-provider/
17
+ * @example ['read:user']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user, adds the ['email'] scope if not present
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * Gitea OAuth Authorization URL
27
+ * @default '/login/oauth/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * Gitea OAuth Token URL
32
+ * @default '/login/oauth/access_token'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * Extra authorization parameters to provide to the authorization URL
37
+ * @see https://docs.gitea.io/en-us/oauth2-provider/
38
+ */
39
+ authorizationParams?: Record<string, string>;
40
+ /**
41
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
42
+ * @default process.env.NUXT_OAUTH_GITEA_REDIRECT_URL
43
+ */
44
+ redirectURL?: string;
45
+ /**
46
+ * URL of your Gitea instance
47
+ * @default 'https://gitea.com'
48
+ */
49
+ baseURL?: string;
50
+ }
51
+ export declare function defineOAuthGiteaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGiteaConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,85 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import {
5
+ handleMissingConfiguration,
6
+ handleAccessTokenErrorResponse,
7
+ getOAuthRedirectURL,
8
+ requestAccessToken
9
+ } from "../utils.js";
10
+ import { useRuntimeConfig, createError } from "#imports";
11
+ export function defineOAuthGiteaEventHandler({
12
+ config,
13
+ onSuccess,
14
+ onError
15
+ }) {
16
+ return eventHandler(async (event) => {
17
+ const runtimeConfig = useRuntimeConfig(event).oauth?.gitea;
18
+ const baseURL = config?.baseURL ?? runtimeConfig.baseURL ?? "https://gitea.com";
19
+ config = defu(config, runtimeConfig, {
20
+ authorizationURL: `${baseURL}/login/oauth/authorize`,
21
+ tokenURL: `${baseURL}/login/oauth/access_token`,
22
+ authorizationParams: {}
23
+ });
24
+ const query = getQuery(event);
25
+ if (query.error) {
26
+ const error = createError({
27
+ statusCode: 401,
28
+ message: `Gitea login failed: ${query.error || "Unknown error"}`,
29
+ data: query
30
+ });
31
+ if (!onError) throw error;
32
+ return onError(event, error);
33
+ }
34
+ if (!config.clientId || !config.clientSecret) {
35
+ return handleMissingConfiguration(
36
+ event,
37
+ "gitea",
38
+ ["clientId", "clientSecret"],
39
+ onError
40
+ );
41
+ }
42
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
43
+ if (!query.code) {
44
+ config.scope = config.scope || [];
45
+ if (!config.scope.length) {
46
+ config.scope.push("read:user");
47
+ }
48
+ if (config.emailRequired && !config.scope.includes("email")) {
49
+ config.scope.push("email");
50
+ }
51
+ return sendRedirect(
52
+ event,
53
+ withQuery(config.authorizationURL, {
54
+ response_type: "code",
55
+ client_id: config.clientId,
56
+ redirect_uri: redirectURL,
57
+ scope: config.scope.join(" "),
58
+ ...config.authorizationParams
59
+ })
60
+ );
61
+ }
62
+ const tokens = await requestAccessToken(config.tokenURL, {
63
+ body: {
64
+ grant_type: "authorization_code",
65
+ client_id: config.clientId,
66
+ client_secret: config.clientSecret,
67
+ redirect_uri: redirectURL,
68
+ code: query.code
69
+ }
70
+ });
71
+ if (tokens.error) {
72
+ return handleAccessTokenErrorResponse(event, "gitea", tokens, onError);
73
+ }
74
+ const accessToken = tokens.access_token;
75
+ const user = await $fetch(`${baseURL}/api/v1/user`, {
76
+ headers: {
77
+ Authorization: `token ${accessToken}`
78
+ }
79
+ });
80
+ return onSuccess(event, {
81
+ user,
82
+ tokens
83
+ });
84
+ });
85
+ }
@@ -76,6 +76,7 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
76
76
  return onError(event, error);
77
77
  }
78
78
  user.email = primaryEmail.email;
79
+ user.email_verified = primaryEmail.verified;
79
80
  }
80
81
  return onSuccess(event, {
81
82
  user,
@@ -16,14 +16,15 @@ export function defineOAuthMicrosoftEventHandler({ config, onSuccess, onError })
16
16
  const tokenURL = config.tokenURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`;
17
17
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
18
  if (!query.code) {
19
- const scope = config.scope && config.scope.length > 0 ? config.scope : ["User.Read"];
19
+ config.scope = config.scope && config.scope.length > 0 ? config.scope : ["User.Read"];
20
+ config.scope = [...new Set(config.scope)];
20
21
  return sendRedirect(
21
22
  event,
22
23
  withQuery(authorizationURL, {
23
24
  client_id: config.clientId,
24
25
  response_type: "code",
25
26
  redirect_uri: redirectURL,
26
- scope: scope.join(" "),
27
+ scope: config.scope.join(" "),
27
28
  ...config.authorizationParams
28
29
  })
29
30
  );
@@ -34,4 +34,29 @@ export declare function requestAccessToken(url: string, options: RequestAccessTo
34
34
  */
35
35
  export declare function handleAccessTokenErrorResponse(event: H3Event, oauthProvider: OAuthProvider, oauthError: any, onError?: OnError): void | Promise<void>;
36
36
  export declare function handleMissingConfiguration(event: H3Event, provider: OAuthProvider, missingKeys: string[], onError?: OnError): void | Promise<void>;
37
+ /**
38
+ * JWT signing using jose
39
+ *
40
+ * @see https://github.com/panva/jose
41
+ */
42
+ interface JWTSignOptions {
43
+ privateKey: string;
44
+ keyId: string;
45
+ teamId?: string;
46
+ clientId?: string;
47
+ algorithm?: 'ES256' | 'RS256';
48
+ expiresIn?: string;
49
+ }
50
+ export declare function signJwt<T extends Record<string, unknown>>(payload: T, options: JWTSignOptions): Promise<string>;
51
+ /**
52
+ * Verify a JWT token using jose - will throw error if invalid
53
+ *
54
+ * @see https://github.com/panva/jose
55
+ */
56
+ interface JWTVerifyOptions {
57
+ publicJwkUrl: string;
58
+ audience: string;
59
+ issuer: string;
60
+ }
61
+ export declare function verifyJwt<T>(token: string, options: JWTVerifyOptions): Promise<T>;
37
62
  export {};
@@ -1,6 +1,7 @@
1
1
  import { getRequestURL } from "h3";
2
2
  import { FetchError } from "ofetch";
3
3
  import { snakeCase, upperFirst } from "scule";
4
+ import * as jose from "jose";
4
5
  import { createError } from "#imports";
5
6
  export function getOAuthRedirectURL(event) {
6
7
  const requestURL = getRequestURL(event);
@@ -44,3 +45,19 @@ export function handleMissingConfiguration(event, provider, missingKeys, onError
44
45
  if (!onError) throw error;
45
46
  return onError(event, error);
46
47
  }
48
+ export async function signJwt(payload, options) {
49
+ const now = Math.floor(Date.now() / 1e3);
50
+ const privateKey = await jose.importPKCS8(
51
+ options.privateKey.replace(/\\n/g, "\n"),
52
+ options.algorithm || "ES256"
53
+ );
54
+ return new jose.SignJWT(payload).setProtectedHeader({ alg: options.algorithm || "ES256", kid: options.keyId }).setIssuedAt(now).setExpirationTime(options.expiresIn || "5m").sign(privateKey);
55
+ }
56
+ export async function verifyJwt(token, options) {
57
+ const JWKS = jose.createRemoteJWKSet(new URL(options.publicJwkUrl));
58
+ const { payload } = await jose.jwtVerify(token, JWKS, {
59
+ audience: options.audience,
60
+ issuer: options.issuer
61
+ });
62
+ return payload;
63
+ }
@@ -1,5 +1,5 @@
1
1
  import type { H3Event, H3Error } from 'h3';
2
- export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
2
+ export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | (string & {});
3
3
  export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
4
4
  export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
5
5
  config?: TConfig;
@@ -45,4 +45,11 @@ export interface UserSessionComposable {
45
45
  * Clear the user session and remove the session cookie.
46
46
  */
47
47
  clear: () => Promise<void>;
48
+ /**
49
+ * Open the OAuth route in a popup that auto-closes when successful.
50
+ */
51
+ openInPopup: (route: string, size?: {
52
+ width?: number;
53
+ height?: number;
54
+ }) => void;
48
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,6 +40,7 @@
40
40
  "defu": "^6.1.4",
41
41
  "h3": "^1.14.0",
42
42
  "hookable": "^5.5.3",
43
+ "jose": "^5.9.6",
43
44
  "ofetch": "^1.4.1",
44
45
  "ohash": "^1.1.4",
45
46
  "openid-client": "^6.1.7",