nuxt-auth-utils 0.5.5 → 0.5.7

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
@@ -214,6 +214,7 @@ It can also be set using environment variables:
214
214
  - GitHub
215
215
  - GitLab
216
216
  - Google
217
+ - Hubspot
217
218
  - Instagram
218
219
  - Keycloak
219
220
  - Linear
@@ -224,6 +225,7 @@ It can also be set using environment variables:
224
225
  - Seznam
225
226
  - Spotify
226
227
  - Steam
228
+ - Strava
227
229
  - TikTok
228
230
  - Twitch
229
231
  - VK
@@ -231,6 +233,7 @@ It can also be set using environment variables:
231
233
  - X (Twitter)
232
234
  - XSUAA
233
235
  - Yandex
236
+ - Zitadel
234
237
 
235
238
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
236
239
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.5",
4
+ "version": "0.5.7",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -157,6 +157,7 @@ const module = defineNuxtModule({
157
157
  clientId: "",
158
158
  clientSecret: "",
159
159
  serverUrl: "",
160
+ serverUrlInternal: "",
160
161
  realm: "",
161
162
  redirectURL: ""
162
163
  });
@@ -249,6 +250,16 @@ const module = defineNuxtModule({
249
250
  clientSecret: "",
250
251
  redirectURL: ""
251
252
  });
253
+ runtimeConfig.oauth.strava = defu(runtimeConfig.oauth.strava, {
254
+ clientId: "",
255
+ clientSecret: "",
256
+ redirectURL: ""
257
+ });
258
+ runtimeConfig.oauth.hubspot = defu(runtimeConfig.oauth.hubspot, {
259
+ clientId: "",
260
+ clientSecret: "",
261
+ redirectURL: ""
262
+ });
252
263
  }
253
264
  });
254
265
 
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -35,10 +35,5 @@ export interface OAuthCognitoConfig {
35
35
  * @default process.env.NUXT_OAUTH_COGNITO_REDIRECT_URL or current URL
36
36
  */
37
37
  redirectURL?: string;
38
- /**
39
- * AWS Cognito App Custom Domain – some pool configurations require this
40
- * @default ''
41
- */
42
- domain?: string;
43
38
  }
44
39
  export declare function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -1,7 +1,8 @@
1
+ import { defu } from "defu";
1
2
  import { eventHandler, getQuery, sendRedirect } from "h3";
3
+ import { discovery } from "openid-client";
2
4
  import { withQuery } from "ufo";
3
- import { defu } from "defu";
4
- import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleMissingConfiguration, requestAccessToken } from "../utils.js";
5
6
  import { useRuntimeConfig } from "#imports";
6
7
  export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }) {
7
8
  return eventHandler(async (event) => {
@@ -11,9 +12,16 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }) {
11
12
  if (!config.clientId || !config.clientSecret || !config.userPoolId || !config.region) {
12
13
  return handleMissingConfiguration(event, "cognito", ["clientId", "clientSecret", "userPoolId", "region"], onError);
13
14
  }
14
- const urlBase = config?.domain || `${config.userPoolId}.auth.${config.region}.amazoncognito.com`;
15
- const authorizationURL = `https://${urlBase}/oauth2/authorize`;
16
- const tokenURL = `https://${urlBase}/oauth2/token`;
15
+ const congitoDiscoveryUrl = new URL(`https://cognito-idp.${config.region}.amazonaws.com/${config.userPoolId}/.well-known/openid-configuration`);
16
+ const issuer = await discovery(congitoDiscoveryUrl, config.clientId, config.clientSecret);
17
+ const {
18
+ authorization_endpoint: authorizationURL,
19
+ token_endpoint: tokenURL,
20
+ userinfo_endpoint: userinfoURL,
21
+ // TODO: implement logout
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ end_session_endpoint: logoutURL
24
+ } = issuer.serverMetadata();
17
25
  const query = getQuery(event);
18
26
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
19
27
  if (!query.code) {
@@ -46,7 +54,7 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }) {
46
54
  }
47
55
  const tokenType = tokens.token_type;
48
56
  const accessToken = tokens.access_token;
49
- const user = await $fetch(`https://${urlBase}/oauth2/userInfo`, {
57
+ const user = await $fetch(userinfoURL, {
50
58
  headers: {
51
59
  Authorization: `${tokenType} ${accessToken}`
52
60
  }
@@ -11,8 +11,8 @@ export function defineOAuthDropboxEventHandler({ config, onSuccess, onError }) {
11
11
  authorizationParams: {}
12
12
  });
13
13
  const query = getQuery(event);
14
- if (!config.clientId) {
15
- return handleMissingConfiguration(event, "dropbox", ["clientId"], onError);
14
+ if (!config.clientId || !config.clientSecret) {
15
+ return handleMissingConfiguration(event, "dropbox", ["clientId", "clientSecret"], onError);
16
16
  }
17
17
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
18
  if (!query.code) {
@@ -24,8 +24,8 @@ export function defineOAuthFacebookEventHandler({
24
24
  if (!onError) throw error;
25
25
  return onError(event, error);
26
26
  }
27
- if (!config.clientId) {
28
- return handleMissingConfiguration(event, "facebook", ["clientId"], onError);
27
+ if (!config.clientId || !config.clientSecret) {
28
+ return handleMissingConfiguration(event, "facebook", ["clientId", "clientSecret"], onError);
29
29
  }
30
30
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
31
31
  if (!query.code) {
@@ -16,8 +16,8 @@ export function defineOAuthGoogleEventHandler({
16
16
  authorizationParams: {}
17
17
  });
18
18
  const query = getQuery(event);
19
- if (!config.clientId) {
20
- return handleMissingConfiguration(event, "google", ["clientId"], onError);
19
+ if (!config.clientId || !config.clientSecret) {
20
+ return handleMissingConfiguration(event, "google", ["clientId", "clientSecret"], onError);
21
21
  }
22
22
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
23
23
  if (!query.code) {
@@ -0,0 +1,26 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthHubspotConfig {
3
+ /**
4
+ * Hubspot OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_HUBSPOT_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Hubspot OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_HUBSPOT_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Hubspot OAuth Redirect URL
15
+ * @default process.env.NUXT_OAUTH_HUBSPOT_REDIRECT_URL
16
+ */
17
+ redirectURL?: string;
18
+ /**
19
+ * Hubspot OAuth Scope
20
+ * @default ['oauth']
21
+ * @see https://developers.hubspot.com/beta-docs/guides/apps/authentication/scopes
22
+ * @example ['accounting', 'automation', 'actions']
23
+ */
24
+ scope?: string[];
25
+ }
26
+ export declare function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthHubspotConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,57 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import defu from "defu";
4
+ import {
5
+ getOAuthRedirectURL,
6
+ handleAccessTokenErrorResponse,
7
+ handleMissingConfiguration,
8
+ requestAccessToken
9
+ } from "../utils.js";
10
+ import { useRuntimeConfig } from "#imports";
11
+ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }) {
12
+ return eventHandler(async (event) => {
13
+ config = defu(config, useRuntimeConfig(event).oauth?.hubspot);
14
+ if (!config.clientId || !config.clientSecret || !config.redirectURL) {
15
+ return handleMissingConfiguration(event, "hubspot", ["clientId", "clientSecret", "redirectURL"], onError);
16
+ }
17
+ const query = getQuery(event);
18
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
19
+ if (query.error) {
20
+ return handleAccessTokenErrorResponse(event, "hubspot", query, onError);
21
+ }
22
+ if (!query.code) {
23
+ return sendRedirect(
24
+ event,
25
+ withQuery("https://app.hubspot.com/oauth/authorize", {
26
+ client_id: config.clientId,
27
+ redirect_uri: redirectURL,
28
+ scope: config.scope?.join(" ") || "oauth"
29
+ })
30
+ );
31
+ }
32
+ const tokens = await requestAccessToken(
33
+ "https://api.hubapi.com/oauth/v1/token",
34
+ {
35
+ body: {
36
+ client_id: config.clientId,
37
+ client_secret: config.clientSecret,
38
+ code: query.code,
39
+ redirect_uri: redirectURL,
40
+ grant_type: "authorization_code"
41
+ }
42
+ }
43
+ );
44
+ if (tokens.error) {
45
+ return handleAccessTokenErrorResponse(event, "hubspot", tokens, onError);
46
+ }
47
+ const info = await $fetch("https://api.hubapi.com/oauth/v1/access-tokens/" + tokens.access_token);
48
+ return onSuccess(event, {
49
+ user: {
50
+ id: info.user_id,
51
+ email: info.user,
52
+ domain: info.hub_domain
53
+ },
54
+ tokens
55
+ });
56
+ });
57
+ }
@@ -12,21 +12,21 @@ export interface OAuthInstagramConfig {
12
12
  clientSecret?: string;
13
13
  /**
14
14
  * Instagram OAuth Scope
15
- * @default [ 'user_profile' ]
15
+ * @required [ 'business_basic' ]
16
16
  * @see https://developers.facebook.com/docs/instagram-basic-display-api/overview#permissions
17
- * @example [ 'user_profile', 'user_media' ],
17
+ * @example [ 'business_basic', 'business_manage_messages' ],
18
18
  */
19
- scope?: string[];
19
+ scope?: ('business_basic' | 'business_content_publish' | 'business_manage_comments' | 'business_manage_messages')[];
20
20
  /**
21
21
  * Instagram OAuth User Fields
22
22
  * @default [ 'id', 'username'],
23
23
  * @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user#fields
24
- * @example [ 'id', 'username', 'account_type', 'media_count' ],
24
+ * @example [ 'id', 'username', 'user_id', 'account_type', 'profile_picture_url' ],
25
25
  */
26
- fields?: string[];
26
+ fields?: ('id' | 'user_id' | 'username' | 'name' | 'account_type' | 'profile_picture_url' | 'followers_count' | 'follows_count' | 'media_count')[];
27
27
  /**
28
28
  * Instagram OAuth Authorization URL
29
- * @default 'https://api.instagram.com/oauth/authorize'
29
+ * @default 'https://www.instagram.com/oauth/authorize'
30
30
  */
31
31
  authorizationURL?: string;
32
32
  /**
@@ -10,8 +10,8 @@ export function defineOAuthInstagramEventHandler({
10
10
  }) {
11
11
  return eventHandler(async (event) => {
12
12
  config = defu(config, useRuntimeConfig(event).oauth?.instagram, {
13
- scope: ["user_profile"],
14
- authorizationURL: "https://api.instagram.com/oauth/authorize",
13
+ scope: ["business_basic"],
14
+ authorizationURL: "https://www.instagram.com/oauth/authorize",
15
15
  tokenURL: "https://api.instagram.com/oauth/access_token",
16
16
  authorizationParams: {}
17
17
  });
@@ -20,13 +20,17 @@ export function defineOAuthInstagramEventHandler({
20
20
  const error = createError({
21
21
  statusCode: 401,
22
22
  message: `Instagram login failed: ${query.error || "Unknown error"}`,
23
- data: query
23
+ data: {
24
+ error: query.error,
25
+ error_reason: query.error_reason,
26
+ error_description: query.error_description
27
+ }
24
28
  });
25
29
  if (!onError) throw error;
26
30
  return onError(event, error);
27
31
  }
28
- if (!config.clientId) {
29
- return handleMissingConfiguration(event, "instagram", ["clientId"], onError);
32
+ if (!config.clientId || !config.clientSecret) {
33
+ return handleMissingConfiguration(event, "instagram", ["clientId", "clientSecret"], onError);
30
34
  }
31
35
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
32
36
  if (!query.code) {
@@ -36,7 +40,7 @@ export function defineOAuthInstagramEventHandler({
36
40
  withQuery(config.authorizationURL, {
37
41
  client_id: config.clientId,
38
42
  redirect_uri: redirectURL,
39
- scope: config.scope.join(" "),
43
+ scope: config.scope.join(),
40
44
  response_type: "code"
41
45
  })
42
46
  );
@@ -55,9 +59,9 @@ export function defineOAuthInstagramEventHandler({
55
59
  }
56
60
  const accessToken = tokens.access_token;
57
61
  config.fields = config.fields || ["id", "username"];
58
- const fields = config.fields.join(",");
62
+ const fields = config.fields.join();
59
63
  const user = await $fetch(
60
- `https://graph.instagram.com/v20.0/me?fields=${fields}&access_token=${accessToken}`
64
+ `https://graph.instagram.com/v21.0/me?fields=${fields}&access_token=${accessToken}`
61
65
  );
62
66
  if (!user) {
63
67
  throw new Error("Instagram login failed: no user found");
@@ -12,10 +12,17 @@ export interface OAuthKeycloakConfig {
12
12
  clientSecret?: string;
13
13
  /**
14
14
  * Keycloak OAuth Server URL
15
- * @example http://192.168.1.10:8080/auth
15
+ * @example http://192.168.1.10:8080
16
16
  * @default process.env.NUXT_OAUTH_KEYCLOAK_SERVER_URL
17
17
  */
18
18
  serverUrl?: string;
19
+ /**
20
+ * Optional Keycloak OAuth Server URL to use internally, e.g. if Nuxt connects to a Docker hostname while the browser
21
+ * redirect goes to localhost
22
+ * @example http://keycloak:8080
23
+ * @default process.env.NUXT_OAUTH_KEYCLOAK_SERVER_URL_INTERNAL
24
+ */
25
+ serverUrlInternal?: string;
19
26
  /**
20
27
  * Keycloak OAuth Realm
21
28
  * @default process.env.NUXT_OAUTH_KEYCLOAK_REALM
@@ -33,7 +40,7 @@ export interface OAuthKeycloakConfig {
33
40
  */
34
41
  authorizationParams?: Record<string, string>;
35
42
  /**
36
- * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
43
+ * Redirect URL to allow overriding for situations like prod failing to determine public hostname
37
44
  * @default process.env.NUXT_OAUTH_KEYCLOAK_REDIRECT_URL or current URL
38
45
  */
39
46
  redirectURL?: string;
@@ -26,8 +26,9 @@ export function defineOAuthKeycloakEventHandler({
26
26
  return handleMissingConfiguration(event, "keycloak", ["clientId", "clientSecret", "serverUrl", "realm"], onError);
27
27
  }
28
28
  const realmURL = `${config.serverUrl}/realms/${config.realm}`;
29
+ const realmURLInternal = `${config.serverUrlInternal || config.serverUrl}/realms/${config.realm}`;
29
30
  const authorizationURL = `${realmURL}/protocol/openid-connect/auth`;
30
- const tokenURL = `${realmURL}/protocol/openid-connect/token`;
31
+ const tokenURL = `${realmURLInternal}/protocol/openid-connect/token`;
31
32
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
32
33
  if (!query.code) {
33
34
  config.scope = config.scope || ["openid"];
@@ -12,8 +12,8 @@ export function defineOAuthPaypalEventHandler({ config, onSuccess, onError }) {
12
12
  authorizationParams: {}
13
13
  });
14
14
  const query = getQuery(event);
15
- if (!config.clientId) {
16
- return handleMissingConfiguration(event, "paypal", ["clientId"], onError);
15
+ if (!config.clientId || !config.clientSecret) {
16
+ return handleMissingConfiguration(event, "paypal", ["clientId", "clientSecret"], onError);
17
17
  }
18
18
  let paypalAPI = "api-m.paypal.com";
19
19
  if (config.sandbox) {
@@ -92,4 +92,4 @@ export interface OAuthSeznamUser {
92
92
  */
93
93
  gender?: string | null;
94
94
  }
95
- export declare function defineOAUthSeznamEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthSeznamConfig, OAuthSeznamUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
95
+ export declare function defineOAuthSeznamEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthSeznamConfig, OAuthSeznamUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -3,16 +3,16 @@ import { eventHandler, getQuery, sendRedirect } from "h3";
3
3
  import { withQuery } from "ufo";
4
4
  import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleMissingConfiguration, requestAccessToken } from "../utils.js";
5
5
  import { useRuntimeConfig } from "#imports";
6
- export function defineOAUthSeznamEventHandler({ config, onSuccess, onError }) {
6
+ export function defineOAuthSeznamEventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth?.google, {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.seznam, {
9
9
  authorizationURL: "https://login.szn.cz/api/v1/oauth/auth",
10
10
  tokenURL: "https://login.szn.cz/api/v1/oauth/token",
11
11
  userURL: "https://login.szn.cz/api/v1/user"
12
12
  });
13
13
  const query = getQuery(event);
14
- if (!config.clientId) {
15
- return handleMissingConfiguration(event, "seznam", ["clientId"], onError);
14
+ if (!config.clientId || !config.clientSecret) {
15
+ return handleMissingConfiguration(event, "seznam", ["clientId", "clientSecret"], onError);
16
16
  }
17
17
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
18
  if (!query.code) {
@@ -0,0 +1,116 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthStravaConfig {
3
+ /**
4
+ * Strava OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_STRAVA_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Strava OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_STRAVA_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Strava OAuth Scope
15
+ * @default []
16
+ * @see https://developers.strava.com/docs/authentication/ # Details About Requesting Access
17
+ * @example ['read', 'read_all', 'profile:read_all', 'profile:write', 'activity:read', 'activity:read_all', 'activity:write']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Redirect URL to allow overriding for situations like prod failing to determine public hostname
22
+ * @default process.env.NUXT_OAUTH_STRAVA_REDIRECT_URL or current URL
23
+ */
24
+ redirectURL?: string;
25
+ /**
26
+ * To show the authorization prompt to the user, 'force' will always show the prompt
27
+ * @default 'auto'
28
+ * @see https://developers.strava.com/docs/authentication/ # Details About Requesting Access
29
+ */
30
+ approvalPrompt?: 'auto' | 'force';
31
+ }
32
+ export interface OAuthStravaUser {
33
+ /**
34
+ * The unique identifier of the athlete
35
+ */
36
+ id: number;
37
+ /**
38
+ * The username of the athlete
39
+ */
40
+ username: string;
41
+ /**
42
+ * Resource state, indicates level of detail.
43
+ * - Meta (1): Basic information
44
+ * - Summary (2): Summary information
45
+ * - Detail (3): Detailed information
46
+ * @see https://developers.strava.com/docs/reference/#api-models-DetailedAthlete
47
+ */
48
+ resource_state: 1 | 2 | 3;
49
+ /**
50
+ * The athlete's first name
51
+ */
52
+ firstname: string;
53
+ /**
54
+ * The athlete's last name
55
+ */
56
+ lastname: string;
57
+ /**
58
+ * The athlete's bio
59
+ */
60
+ bio: string;
61
+ /**
62
+ * The athlete's city
63
+ */
64
+ city: string;
65
+ /**
66
+ * The athlete's state or geographical region
67
+ */
68
+ state: string;
69
+ /**
70
+ * The athlete's country
71
+ */
72
+ country: string;
73
+ /**
74
+ * The athlete's sex
75
+ */
76
+ sex: string;
77
+ /**
78
+ * Whether the athlete has any Summit subscription
79
+ * @see https://developers.strava.com/docs/reference/#api-models-DetailedAthlete
80
+ */
81
+ summit: boolean;
82
+ /**
83
+ * The time at which the athlete was created
84
+ */
85
+ created_at: Date;
86
+ /**
87
+ * The time at which the athlete was last updated
88
+ */
89
+ updated_at: Date;
90
+ /**
91
+ * The athlete's weight
92
+ */
93
+ weight: number;
94
+ /**
95
+ * URL to a 124x124 pixel profile picture
96
+ */
97
+ profile_medium: string;
98
+ /**
99
+ * URL to a 62x62 pixel profile picture
100
+ */
101
+ profile: string;
102
+ /**
103
+ * The athlete's timezone
104
+ */
105
+ timezone: string;
106
+ }
107
+ export interface OAuthStravaTokens {
108
+ token_type: 'Bearer';
109
+ expires_at: number;
110
+ expires_in: number;
111
+ access_token: string;
112
+ refresh_token: string;
113
+ athlete: OAuthStravaUser;
114
+ error?: string;
115
+ }
116
+ export declare function defineOAuthStravaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthStravaConfig, OAuthStravaUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,73 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import {
5
+ getOAuthRedirectURL,
6
+ handleAccessTokenErrorResponse,
7
+ handleMissingConfiguration,
8
+ requestAccessToken
9
+ } from "../utils.js";
10
+ import { useRuntimeConfig, createError } from "#imports";
11
+ export function defineOAuthStravaEventHandler({
12
+ config,
13
+ onSuccess,
14
+ onError
15
+ }) {
16
+ return eventHandler(async (event) => {
17
+ config = defu(config, useRuntimeConfig(event).oauth?.strava);
18
+ const query = getQuery(event);
19
+ if (query.error) {
20
+ const error = createError({
21
+ statusCode: 401,
22
+ message: `Strava login failed: ${query.error || "Unknown error"}`,
23
+ data: query
24
+ });
25
+ if (!onError) throw error;
26
+ return onError(event, error);
27
+ }
28
+ if (!config.clientId || !config.clientSecret) {
29
+ return handleMissingConfiguration(
30
+ event,
31
+ "strava",
32
+ ["clientId", "clientSecret"],
33
+ onError
34
+ );
35
+ }
36
+ const authorizationURL = "https://www.strava.com/oauth/authorize";
37
+ const tokenURL = "https://www.strava.com/oauth/token";
38
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
39
+ if (!query.code) {
40
+ return sendRedirect(
41
+ event,
42
+ withQuery(authorizationURL, {
43
+ client_id: config.clientId,
44
+ redirect_uri: redirectURL,
45
+ response_type: "code",
46
+ approval_prompt: config.approvalPrompt || "auto",
47
+ scope: config.scope
48
+ })
49
+ );
50
+ }
51
+ const tokens = await requestAccessToken(tokenURL, {
52
+ body: {
53
+ client_id: config.clientId,
54
+ client_secret: config.clientSecret,
55
+ code: query.code,
56
+ grant_type: "authorization_code",
57
+ redirect_uri: redirectURL
58
+ }
59
+ });
60
+ if (tokens.error) {
61
+ return handleAccessTokenErrorResponse(event, "strava", tokens, onError);
62
+ }
63
+ const user = await $fetch("https://www.strava.com/api/v3/athlete", {
64
+ headers: {
65
+ Authorization: `Bearer ${tokens.access_token}`
66
+ }
67
+ });
68
+ return onSuccess(event, {
69
+ user,
70
+ tokens
71
+ });
72
+ });
73
+ }
@@ -11,8 +11,8 @@ export function defineOAuthTwitchEventHandler({ config, onSuccess, onError }) {
11
11
  authorizationParams: {}
12
12
  });
13
13
  const query = getQuery(event);
14
- if (!config.clientId) {
15
- return handleMissingConfiguration(event, "twitch", ["clientId"], onError);
14
+ if (!config.clientId || !config.clientSecret) {
15
+ return handleMissingConfiguration(event, "twitch", ["clientId", "clientSecret"], onError);
16
16
  }
17
17
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
18
  if (!query.code) {
@@ -5,22 +5,27 @@ import type { OAuthConfig } from '#auth-utils';
5
5
  */
6
6
  export interface OAuthWorkOSConfig {
7
7
  /**
8
- * WorkOS OAuth Client ID
8
+ * WorkOS OAuth Client ID *required
9
9
  * @default process.env.NUXT_OAUTH_WORKOS_CLIENT_ID
10
10
  */
11
11
  clientId?: string;
12
12
  /**
13
- * WorkOS OAuth Client Secret (API Key)
13
+ * WorkOS OAuth Client Secret (API Key) *required
14
14
  * @default process.env.NUXT_OAUTH_WORKOS_CLIENT_SECRET
15
15
  */
16
16
  clientSecret?: string;
17
17
  /**
18
- * WorkOS OAuth Connection ID (Not required for WorkOS)
18
+ * WorkOS OAuth Organization ID *not required
19
+ * @default process.env.NUXT_OAUTH_WORKOS_ORGANIZATION_ID
20
+ */
21
+ organizationId?: string;
22
+ /**
23
+ * WorkOS OAuth Connection ID *not required
19
24
  * @default process.env.NUXT_OAUTH_WORKOS_CONNECTION_ID
20
25
  */
21
26
  connectionId?: string;
22
27
  /**
23
- * WorkOS OAuth screen hint
28
+ * WorkOS OAuth screen hint *not required
24
29
  * @default 'sign-in'
25
30
  */
26
31
  screenHint?: 'sign-in' | 'sign-up';
@@ -21,14 +21,13 @@ export function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }) {
21
21
  response_type: "code",
22
22
  provider: "authkit",
23
23
  client_id: config.clientId,
24
- redirect_uri: redirectURL,
25
- connection_id: config.connectionId,
26
- screen_hint: config.screenHint
24
+ redirect_uri: redirectURL || void 0,
25
+ connection_id: config.connectionId || void 0,
26
+ screen_hint: config.screenHint || "sign-in",
27
+ organization_id: config.organizationId || void 0
27
28
  })
28
29
  );
29
30
  }
30
- const ip_address = getRequestIP(event);
31
- const user_agent = getRequestHeader(event, "user-agent");
32
31
  const authenticateResponse = await requestAccessToken("https://api.workos.com/user_management/authenticate", {
33
32
  headers: {
34
33
  "Content-Type": "application/json"
@@ -38,8 +37,8 @@ export function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }) {
38
37
  client_id: config.clientId,
39
38
  client_secret: config.clientSecret,
40
39
  redirect_uri: redirectURL,
41
- ip_address,
42
- user_agent,
40
+ ip_address: getRequestIP(event),
41
+ user_agent: getRequestHeader(event, "user-agent"),
43
42
  code: query.code
44
43
  }
45
44
  });
@@ -25,8 +25,8 @@ export function defineOAuthXEventHandler({
25
25
  }
26
26
  });
27
27
  const query = getQuery(event);
28
- if (!config.clientId) {
29
- return handleMissingConfiguration(event, "x", ["clientId"], onError);
28
+ if (!config.clientId || !config.clientSecret) {
29
+ return handleMissingConfiguration(event, "x", ["clientId", "clientSecret"], onError);
30
30
  }
31
31
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
32
32
  if (!query.code) {
@@ -59,7 +59,6 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }) {
59
59
  Accept: "application/json"
60
60
  }
61
61
  });
62
- console.log("Zitadel user:", user);
63
62
  if (!user) {
64
63
  const error = createError({
65
64
  statusCode: 500,
@@ -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' | 'seznam' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
2
+ export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | '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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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.2",
11
+ "packageManager": "pnpm@9.15.0",
12
12
  "exports": {
13
13
  ".": {
14
14
  "types": "./dist/types.d.ts",
@@ -36,12 +36,13 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@adonisjs/hash": "^9.0.5",
39
- "@nuxt/kit": "^3.14.159",
39
+ "@nuxt/kit": "^3.14.1592",
40
40
  "defu": "^6.1.4",
41
41
  "h3": "^1.13.0",
42
42
  "hookable": "^5.5.3",
43
43
  "ofetch": "^1.4.1",
44
44
  "ohash": "^1.1.4",
45
+ "openid-client": "^6.1.7",
45
46
  "pathe": "^1.1.2",
46
47
  "scule": "^1.3.0",
47
48
  "uncrypto": "^0.1.3"
@@ -59,20 +60,20 @@
59
60
  }
60
61
  },
61
62
  "devDependencies": {
62
- "@iconify-json/simple-icons": "^1.2.11",
63
+ "@iconify-json/simple-icons": "^1.2.15",
63
64
  "@nuxt/devtools": "latest",
64
- "@nuxt/eslint-config": "^0.6.1",
65
+ "@nuxt/eslint-config": "^0.7.3",
65
66
  "@nuxt/module-builder": "^0.8.4",
66
- "@nuxt/schema": "^3.14.159",
67
- "@nuxt/test-utils": "^3.14.4",
68
- "@nuxt/ui": "^2.19.2",
69
- "@nuxt/ui-pro": "^1.5.0",
67
+ "@nuxt/schema": "^3.14.1592",
68
+ "@nuxt/test-utils": "^3.15.1",
69
+ "@nuxt/ui": "^2.20.0",
70
+ "@nuxt/ui-pro": "^1.6.0",
70
71
  "@simplewebauthn/types": "^11.0.0",
71
72
  "changelogen": "^0.5.7",
72
- "eslint": "^9.14.0",
73
- "nuxt": "^3.14.159",
73
+ "eslint": "^9.16.0",
74
+ "nuxt": "^3.14.1592",
74
75
  "typescript": "^5.6.3",
75
- "vitest": "^2.1.5",
76
+ "vitest": "^2.1.8",
76
77
  "vue-tsc": "^2.1.10"
77
78
  }
78
79
  }