nuxt-auth-utils 0.5.4 → 0.5.5

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
@@ -221,11 +221,13 @@ It can also be set using environment variables:
221
221
  - Microsoft
222
222
  - PayPal
223
223
  - Polar
224
+ - Seznam
224
225
  - Spotify
225
226
  - Steam
226
227
  - TikTok
227
228
  - Twitch
228
229
  - VK
230
+ - WorkOS
229
231
  - X (Twitter)
230
232
  - XSUAA
231
233
  - Yandex
@@ -349,10 +351,21 @@ The following code does not include the actual database queries, but shows the g
349
351
  import { z } from 'zod'
350
352
  export default defineWebAuthnRegisterEventHandler({
351
353
  // optional
352
- validateUser: z.object({
353
- // we want the userName to be a valid email
354
- userName: z.string().email()
355
- }).parse,
354
+ async validateUser(userBody, event) {
355
+ // bonus: check if the user is already authenticated to link a credential to his account
356
+ // We first check if the user is already authenticated by getting the session
357
+ // And verify that the email is the same as the one in session
358
+ const session = await getUserSession(event)
359
+ if (session.user?.email && session.user.email !== body.userName) {
360
+ throw createError({ statusCode: 400, message: 'Email not matching curent session' })
361
+ }
362
+
363
+ // If he registers a new account with credentials
364
+ return z.object({
365
+ // we want the userName to be a valid email
366
+ userName: z.string().email()
367
+ }).parse(userBody)
368
+ },
356
369
  async onSuccess(event, { credential, user }) {
357
370
  // The credential creation has been successful
358
371
  // We need to create a user if it does not exist
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -126,6 +126,13 @@ const module = defineNuxtModule({
126
126
  audience: "",
127
127
  redirectURL: ""
128
128
  });
129
+ runtimeConfig.oauth.workos = defu(runtimeConfig.oauth.workos, {
130
+ clientId: "",
131
+ clientSecret: "",
132
+ connectionId: "",
133
+ screenHint: "",
134
+ redirectURL: ""
135
+ });
129
136
  runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
130
137
  clientId: "",
131
138
  clientSecret: "",
@@ -237,6 +244,11 @@ const module = defineNuxtModule({
237
244
  domain: "",
238
245
  redirectURL: ""
239
246
  });
247
+ runtimeConfig.oauth.seznam = defu(runtimeConfig.oauth.seznam, {
248
+ clientId: "",
249
+ clientSecret: "",
250
+ redirectURL: ""
251
+ });
240
252
  }
241
253
  });
242
254
 
@@ -0,0 +1,95 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthSeznamConfig {
3
+ /**
4
+ * Seznam OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_SEZNAM_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Seznam OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_SEZNAM_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Seznam OAuth Scope
15
+ * @default ['identity']
16
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
17
+ * @example ['identity', 'avatar']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
22
+ * Redirect URL has to be set as well in the Seznam OAuth settings https://vyvojari.seznam.cz/oauth/admin in order for it to work
23
+ * @see https://vyvojari.seznam.cz/oauth/admin
24
+ * @default process.env.NUXT_OAUTH_SEZNAM_REDIRECT_URL
25
+ */
26
+ redirectURL?: string;
27
+ /**
28
+ * Seznam OAuth Authorization URL
29
+ * @default 'https://login.szn.cz/api/v1/oauth/auth'
30
+ */
31
+ authorizationURL?: string;
32
+ /**
33
+ * Seznam OAuth Token URL
34
+ * @default 'https://login.szn.cz/api/v1/oauth/token'
35
+ */
36
+ tokenURL?: string;
37
+ /**
38
+ * Seznam OAuth User URL
39
+ * @default 'https://login.szn.cz/api/v1/user'
40
+ */
41
+ userURL?: string;
42
+ }
43
+ export interface OAuthSeznamUser {
44
+ /**
45
+ * Unique persistent user account identifier
46
+ */
47
+ oauth_user_id: string;
48
+ /**
49
+ * The user's e-mail address or null for users without an e-mail
50
+ */
51
+ email: string | null;
52
+ /**
53
+ * Given name (when available)
54
+ */
55
+ firstname: string;
56
+ /**
57
+ * Family name (when available)
58
+ */
59
+ lastname: string;
60
+ /**
61
+ * User account identifier suitable for usage within the Seznam ad infrastructure
62
+ */
63
+ advert_user_id: string;
64
+ /**
65
+ * (only when provided and validated by the user; null otherwise)
66
+ * Available only when you use the contact-phone scope
67
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
68
+ */
69
+ contact_phone?: string | null;
70
+ /**
71
+ * the image's URL
72
+ * Available only when you use the avatar scope
73
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
74
+ */
75
+ avatar_url?: string | null;
76
+ /**
77
+ * true/false value corresponding to the user's adult status
78
+ * Available only when you use the avatar scope
79
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
80
+ */
81
+ adulthood?: boolean;
82
+ /**
83
+ * date of birth in the ISO 8601 format (only when present; null otherwise)
84
+ * Available only when you use the birthday scope
85
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
86
+ */
87
+ birthday?: string | null;
88
+ /**
89
+ * one of the allowed strings "Male" / "Female" / "Other" (null if not set)
90
+ * Available only when you use the gender scope
91
+ * @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
92
+ */
93
+ gender?: string | null;
94
+ }
95
+ export declare function defineOAUthSeznamEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthSeznamConfig, OAuthSeznamUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,57 @@
1
+ import defu from "defu";
2
+ import { eventHandler, getQuery, sendRedirect } from "h3";
3
+ import { withQuery } from "ufo";
4
+ import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleMissingConfiguration, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function defineOAUthSeznamEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.google, {
9
+ authorizationURL: "https://login.szn.cz/api/v1/oauth/auth",
10
+ tokenURL: "https://login.szn.cz/api/v1/oauth/token",
11
+ userURL: "https://login.szn.cz/api/v1/user"
12
+ });
13
+ const query = getQuery(event);
14
+ if (!config.clientId) {
15
+ return handleMissingConfiguration(event, "seznam", ["clientId"], onError);
16
+ }
17
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
+ if (!query.code) {
19
+ config.scope = config.scope || ["identity"];
20
+ return sendRedirect(
21
+ event,
22
+ withQuery(config.authorizationURL, {
23
+ response_type: "code",
24
+ client_id: config.clientId,
25
+ redirect_uri: redirectURL,
26
+ scope: config.scope.join(","),
27
+ state: query.state || ""
28
+ })
29
+ );
30
+ }
31
+ const tokens = await requestAccessToken(config.tokenURL, {
32
+ body: {
33
+ grant_type: "authorization_code",
34
+ code: query.code,
35
+ client_id: config.clientId,
36
+ client_secret: config.clientSecret,
37
+ redirect_uri: redirectURL
38
+ }
39
+ });
40
+ if (tokens.error) {
41
+ return handleAccessTokenErrorResponse(event, "seznam", tokens, onError);
42
+ }
43
+ const accessToken = tokens.access_token;
44
+ const user = await $fetch(
45
+ config.userURL,
46
+ {
47
+ headers: {
48
+ Authorization: `bearer ${accessToken}`
49
+ }
50
+ }
51
+ );
52
+ return onSuccess(event, {
53
+ tokens,
54
+ user
55
+ });
56
+ });
57
+ }
@@ -0,0 +1,58 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ /**
3
+ * WorkOS OAuth Configuration
4
+ * @see https://workos.com/docs/reference/user-management/authentication
5
+ */
6
+ export interface OAuthWorkOSConfig {
7
+ /**
8
+ * WorkOS OAuth Client ID
9
+ * @default process.env.NUXT_OAUTH_WORKOS_CLIENT_ID
10
+ */
11
+ clientId?: string;
12
+ /**
13
+ * WorkOS OAuth Client Secret (API Key)
14
+ * @default process.env.NUXT_OAUTH_WORKOS_CLIENT_SECRET
15
+ */
16
+ clientSecret?: string;
17
+ /**
18
+ * WorkOS OAuth Connection ID (Not required for WorkOS)
19
+ * @default process.env.NUXT_OAUTH_WORKOS_CONNECTION_ID
20
+ */
21
+ connectionId?: string;
22
+ /**
23
+ * WorkOS OAuth screen hint
24
+ * @default 'sign-in'
25
+ */
26
+ screenHint?: 'sign-in' | 'sign-up';
27
+ /**
28
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
29
+ * @default process.env.NUXT_OAUTH_WORKOS_REDIRECT_URL or current URL
30
+ */
31
+ redirectURL?: string;
32
+ }
33
+ export interface OAuthWorkOSUser {
34
+ object: 'user';
35
+ id: string;
36
+ email: string;
37
+ first_name: string | null;
38
+ last_name: string | null;
39
+ email_verified: boolean;
40
+ profile_picture_url: string | null;
41
+ created_at: string;
42
+ updated_at: string;
43
+ }
44
+ export type OAuthWorkOSAuthenticationMethod = 'SSO' | 'Password' | 'AppleOAuth' | 'GitHubOAuth' | 'GoogleOAuth' | 'MicrosoftOAuth' | 'MagicAuth' | 'Impersonation';
45
+ export interface OAuthWorkOSAuthenticateResponse {
46
+ user: OAuthWorkOSUser;
47
+ organization_id: string | null;
48
+ access_token: string;
49
+ refresh_token: string;
50
+ error: string | null;
51
+ error_description: string | null;
52
+ authentication_method: OAuthWorkOSAuthenticationMethod;
53
+ }
54
+ export interface OAuthWorkOSTokens {
55
+ access_token: string;
56
+ refresh_token: string;
57
+ }
58
+ export declare function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthWorkOSConfig, OAuthWorkOSUser, OAuthWorkOSTokens>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,54 @@
1
+ import { eventHandler, getQuery, sendRedirect, getRequestIP, getRequestHeader } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.workos, { screen_hint: "sign-in" });
9
+ if (!config.clientId || !config.clientSecret) {
10
+ return handleMissingConfiguration(event, "workos", ["clientId", "clientSecret"], onError);
11
+ }
12
+ const query = getQuery(event);
13
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
14
+ if (query.error) {
15
+ return handleAccessTokenErrorResponse(event, "workos", query, onError);
16
+ }
17
+ if (!query.code) {
18
+ return sendRedirect(
19
+ event,
20
+ withQuery("https://api.workos.com/user_management/authorize", {
21
+ response_type: "code",
22
+ provider: "authkit",
23
+ client_id: config.clientId,
24
+ redirect_uri: redirectURL,
25
+ connection_id: config.connectionId,
26
+ screen_hint: config.screenHint
27
+ })
28
+ );
29
+ }
30
+ const ip_address = getRequestIP(event);
31
+ const user_agent = getRequestHeader(event, "user-agent");
32
+ const authenticateResponse = await requestAccessToken("https://api.workos.com/user_management/authenticate", {
33
+ headers: {
34
+ "Content-Type": "application/json"
35
+ },
36
+ body: {
37
+ grant_type: "authorization_code",
38
+ client_id: config.clientId,
39
+ client_secret: config.clientSecret,
40
+ redirect_uri: redirectURL,
41
+ ip_address,
42
+ user_agent,
43
+ code: query.code
44
+ }
45
+ });
46
+ if (authenticateResponse.error) {
47
+ return handleAccessTokenErrorResponse(event, "workos", authenticateResponse, onError);
48
+ }
49
+ return onSuccess(event, {
50
+ tokens: { access_token: authenticateResponse.access_token, refresh_token: authenticateResponse.refresh_token },
51
+ user: authenticateResponse.user
52
+ });
53
+ });
54
+ }
@@ -35,7 +35,7 @@ export function defineOAuthXSUAAEventHandler({ config, onSuccess, onError }) {
35
35
  }
36
36
  });
37
37
  if (tokens.error) {
38
- return handleAccessTokenErrorResponse(event, "auth0", tokens, onError);
38
+ return handleAccessTokenErrorResponse(event, "xsuaa", tokens, onError);
39
39
  }
40
40
  const tokenType = tokens.token_type;
41
41
  const accessToken = tokens.access_token;
@@ -12,6 +12,7 @@ export interface RequestAccessTokenBody {
12
12
  redirect_uri: string;
13
13
  client_id: string;
14
14
  client_secret?: string;
15
+ [key: string]: string | undefined;
15
16
  }
16
17
  interface RequestAccessTokenOptions {
17
18
  body?: RequestAccessTokenBody;
@@ -1,7 +1,8 @@
1
- import type { ValidateFunction } from 'h3';
1
+ import type { H3Event } from 'h3';
2
2
  import type { WebAuthnUser, WebAuthnRegisterEventHandlerOptions } from '#auth-utils';
3
+ import type { ValidateUserFunction } from '~/src/runtime/types/webauthn';
3
4
  export declare function defineWebAuthnRegisterEventHandler<T extends WebAuthnUser>({ storeChallenge, getChallenge, getOptions, validateUser, excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedRegistrationResponse | {
4
5
  creationOptions: import("@simplewebauthn/types").PublicKeyCredentialCreationOptionsJSON;
5
6
  attemptId: string;
6
7
  }>>;
7
- export declare function validateUserData<T>(data: unknown, fn: ValidateFunction<T>): Promise<T>;
8
+ export declare function validateUserData<T>(userBody: WebAuthnUser, event: H3Event, fn: ValidateUserFunction<T>): Promise<T>;
@@ -23,7 +23,7 @@ export function defineWebAuthnRegisterEventHandler({
23
23
  });
24
24
  let user = body.user;
25
25
  if (validateUser) {
26
- user = await validateUserData(body.user, validateUser);
26
+ user = await validateUserData(body.user, event, validateUser);
27
27
  }
28
28
  const _config = defu(await getOptions?.(event, body) ?? {}, useRuntimeConfig(event).webauthn.register, {
29
29
  rpID: url.hostname,
@@ -97,24 +97,24 @@ export function defineWebAuthnRegisterEventHandler({
97
97
  }
98
98
  });
99
99
  }
100
- export async function validateUserData(data, fn) {
100
+ export async function validateUserData(userBody, event, fn) {
101
101
  try {
102
- const res = await fn(data);
102
+ const res = await fn(userBody, event);
103
103
  if (res === false) {
104
104
  throw createUserValidationError();
105
105
  }
106
106
  if (res === true) {
107
- return data;
107
+ return userBody;
108
108
  }
109
- return res ?? data;
109
+ return res ?? userBody;
110
110
  } catch (error) {
111
111
  throw createUserValidationError(error);
112
112
  }
113
113
  }
114
114
  function createUserValidationError(validateError) {
115
115
  throw createError({
116
- status: 400,
117
- message: "User Validation Error",
116
+ status: validateError?.statusCode || 400,
117
+ message: validateError?.message || "User Validation Error",
118
118
  data: validateError
119
119
  });
120
120
  }
@@ -1,5 +1,5 @@
1
1
  import type { H3Event, H3Error } from 'h3';
2
- export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
2
+ export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (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;
@@ -1,6 +1,6 @@
1
1
  import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, RegistrationResponseJSON } from '@simplewebauthn/types';
2
2
  import type { Ref } from 'vue';
3
- import type { H3Event, H3Error, ValidateFunction } from 'h3';
3
+ import type { H3Event, H3Error, ValidateResult } from 'h3';
4
4
  import type { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts, VerifiedAuthenticationResponse, VerifiedRegistrationResponse } from '@simplewebauthn/server';
5
5
  export interface WebAuthnCredential {
6
6
  id: string;
@@ -36,13 +36,14 @@ export type RegistrationBody<T extends WebAuthnUser> = {
36
36
  attemptId: string;
37
37
  response: RegistrationResponseJSON;
38
38
  };
39
+ export type ValidateUserFunction<T> = (userBody: WebAuthnUser, event: H3Event) => ValidateResult<T> | Promise<ValidateResult<T>>;
39
40
  export type WebAuthnRegisterEventHandlerOptions<T extends WebAuthnUser> = WebAuthnEventHandlerBase<{
40
41
  user: T;
41
42
  credential: WebAuthnCredential;
42
43
  registrationInfo: Exclude<VerifiedRegistrationResponse['registrationInfo'], undefined>;
43
44
  }> & {
44
45
  getOptions?: (event: H3Event, body: RegistrationBody<T>) => Partial<GenerateRegistrationOptionsOpts> | Promise<Partial<GenerateRegistrationOptionsOpts>>;
45
- validateUser?: ValidateFunction<T>;
46
+ validateUser?: ValidateUserFunction<T>;
46
47
  excludeCredentials?: (event: H3Event, userName: string) => CredentialsList | Promise<CredentialsList>;
47
48
  };
48
49
  export type AuthenticationBody = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "license": "MIT",
10
10
  "type": "module",
11
- "packageManager": "pnpm@9.13.0",
11
+ "packageManager": "pnpm@9.13.2",
12
12
  "exports": {
13
13
  ".": {
14
14
  "types": "./dist/types.d.ts",