nuxt-auth-utils 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +18 -6
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +25 -1
  4. package/dist/runtime/app/components/AuthState.vue +3 -4
  5. package/dist/runtime/server/lib/oauth/auth0.js +2 -3
  6. package/dist/runtime/server/lib/oauth/battledotnet.js +1 -2
  7. package/dist/runtime/server/lib/oauth/cognito.js +2 -3
  8. package/dist/runtime/server/lib/oauth/discord.js +2 -3
  9. package/dist/runtime/server/lib/oauth/facebook.js +1 -2
  10. package/dist/runtime/server/lib/oauth/github.js +2 -3
  11. package/dist/runtime/server/lib/oauth/google.js +2 -3
  12. package/dist/runtime/server/lib/oauth/keycloak.js +2 -3
  13. package/dist/runtime/server/lib/oauth/linkedin.js +2 -3
  14. package/dist/runtime/server/lib/oauth/microsoft.js +2 -3
  15. package/dist/runtime/server/lib/oauth/paypal.d.ts +47 -0
  16. package/dist/runtime/server/lib/oauth/paypal.js +104 -0
  17. package/dist/runtime/server/lib/oauth/spotify.js +2 -3
  18. package/dist/runtime/server/lib/oauth/steam.d.ts +15 -0
  19. package/dist/runtime/server/lib/oauth/steam.js +54 -0
  20. package/dist/runtime/server/lib/oauth/twitch.js +2 -3
  21. package/dist/runtime/server/lib/oauth/x.d.ts +46 -0
  22. package/dist/runtime/server/lib/oauth/x.js +122 -0
  23. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +26 -0
  24. package/dist/runtime/server/lib/oauth/xsuaa.js +73 -0
  25. package/dist/runtime/server/lib/oauth/yandex.d.ts +41 -0
  26. package/dist/runtime/server/lib/oauth/yandex.js +84 -0
  27. package/dist/runtime/server/utils/oauth.d.ts +10 -0
  28. package/dist/runtime/server/utils/oauth.js +11 -1
  29. package/package.json +2 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License][license-src]][license-href]
6
6
  [![Nuxt][nuxt-src]][nuxt-href]
7
7
 
8
- Minimalist Authentication module for Nuxt exposing Vue composables and server utils.
8
+ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
9
9
 
10
10
  - [Release Notes](/CHANGELOG.md)
11
11
  - [Demo](https://github.com/atinux/nuxt-todos-edge)
@@ -14,13 +14,20 @@ Minimalist Authentication module for Nuxt exposing Vue composables and server ut
14
14
 
15
15
  ## Features
16
16
 
17
- - Support for Hybrid Rendering (SSR / CSR / SWR / Prerendering)
18
- - Secured & sealed cookies sessions
19
- - [OAuth Providers](#supported-oauth-providers)
17
+ - [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
18
+ - [15+ OAuth Providers](#supported-oauth-providers)
19
+ - [Vue composable](#vue-composable)
20
+ - [Server utils](#server-utils)
21
+ - [`<AuthState>` component](#authstate-component)
22
+ - [Extendable with hooks](#extend-session)
20
23
 
21
24
  ## Requirements
22
25
 
23
- This module only works with the Nuxt server running as it uses server API routes. This means that you cannot use this module with `nuxt generate`.
26
+ This module only works with a Nuxt server running as it uses server API routes (`nuxt build`).
27
+
28
+ This means that you cannot use this module with `nuxt generate`.
29
+
30
+ You can anyway use [Hybrid Rendering](#hybrid-rendering) to pre-render pages of your application or disable server-side rendering completely.
24
31
 
25
32
  ## Quick Setup
26
33
 
@@ -41,7 +48,7 @@ Nuxt Auth Utils generates one for you when running Nuxt in development the first
41
48
 
42
49
  3. That's it! You can now add authentication to your Nuxt app ✨
43
50
 
44
- ## Vue Composables
51
+ ## Vue Composable
45
52
 
46
53
  Nuxt Auth Utils automatically adds some plugins to fetch the current user session to let you access it from your Vue components.
47
54
 
@@ -153,8 +160,13 @@ It can also be set using environment variables:
153
160
  - Keycloak
154
161
  - LinkedIn
155
162
  - Microsoft
163
+ - PayPal
156
164
  - Spotify
165
+ - Steam
157
166
  - Twitch
167
+ - X (Twitter)
168
+ - XSUAA
169
+ - Yandex
158
170
 
159
171
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
160
172
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.7.1",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -43,7 +43,11 @@ const module = defineNuxtModule({
43
43
  const envPath = join(nuxt.options.rootDir, ".env");
44
44
  const envContent = await readFile(envPath, "utf-8").catch(() => "");
45
45
  if (!envContent.includes("NUXT_SESSION_PASSWORD")) {
46
- await writeFile(envPath, `${envContent ? envContent + "\n" : envContent}NUXT_SESSION_PASSWORD=${runtimeConfig.session.password}`, "utf-8");
46
+ await writeFile(
47
+ envPath,
48
+ `${envContent ? envContent + "\n" : envContent}NUXT_SESSION_PASSWORD=${runtimeConfig.session.password}`,
49
+ "utf-8"
50
+ );
47
51
  }
48
52
  }
49
53
  runtimeConfig.oauth = defu(runtimeConfig.oauth, {});
@@ -106,6 +110,26 @@ const module = defineNuxtModule({
106
110
  clientId: "",
107
111
  clientSecret: ""
108
112
  });
113
+ runtimeConfig.oauth.paypal = defu(runtimeConfig.oauth.paypal, {
114
+ clientId: "",
115
+ clientSecret: ""
116
+ });
117
+ runtimeConfig.oauth.steam = defu(runtimeConfig.oauth.steam, {
118
+ apiKey: ""
119
+ });
120
+ runtimeConfig.oauth.x = defu(runtimeConfig.oauth.x, {
121
+ clientId: "",
122
+ clientSecret: ""
123
+ });
124
+ runtimeConfig.oauth.xsuaa = defu(runtimeConfig.oauth.xsuaa, {
125
+ clientId: "",
126
+ clientSecret: "",
127
+ domain: ""
128
+ });
129
+ runtimeConfig.oauth.yandex = defu(runtimeConfig.oauth.yandex, {
130
+ clientId: "",
131
+ clientSecret: ""
132
+ });
109
133
  }
110
134
  });
111
135
 
@@ -1,14 +1,13 @@
1
- <script setup>
1
+ <script lang="ts" setup>
2
2
  import { useUserSession } from '#imports'
3
3
 
4
- const userSession = useUserSession()
5
- const { ready } = userSession
4
+ const { loggedIn, user, clear, ready } = useUserSession()
6
5
  </script>
7
6
 
8
7
  <template>
9
8
  <slot
10
9
  v-if="ready"
11
- v-bind="userSession"
10
+ v-bind="{ loggedIn, user, clear }"
12
11
  />
13
12
  <slot
14
13
  v-else
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function auth0EventHandler({ config, onSuccess, onError }) {
@@ -40,7 +39,7 @@ export function auth0EventHandler({ config, onSuccess, onError }) {
40
39
  })
41
40
  );
42
41
  }
43
- const tokens = await ofetch(
42
+ const tokens = await $fetch(
44
43
  tokenURL,
45
44
  {
46
45
  method: "POST",
@@ -70,7 +69,7 @@ export function auth0EventHandler({ config, onSuccess, onError }) {
70
69
  }
71
70
  const tokenType = tokens.token_type;
72
71
  const accessToken = tokens.access_token;
73
- const user = await ofetch(`https://${config.domain}/userinfo`, {
72
+ const user = await $fetch(`https://${config.domain}/userinfo`, {
74
73
  headers: {
75
74
  Authorization: `${tokenType} ${accessToken}`
76
75
  }
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
3
- import { ofetch } from "ofetch";
4
3
  import { withQuery, parsePath } from "ufo";
5
4
  import { defu } from "defu";
6
5
  import { useRuntimeConfig } from "#imports";
@@ -88,7 +87,7 @@ export function battledotnetEventHandler({ config, onSuccess, onError }) {
88
87
  return onError(event, error);
89
88
  }
90
89
  const accessToken = tokens.access_token;
91
- const user = await ofetch("https://oauth.battle.net/userinfo", {
90
+ const user = await $fetch("https://oauth.battle.net/userinfo", {
92
91
  headers: {
93
92
  "User-Agent": `Battledotnet-OAuth-${config.clientId}`,
94
93
  "Authorization": `Bearer ${accessToken}`
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function cognitoEventHandler({ config, onSuccess, onError }) {
@@ -34,7 +33,7 @@ export function cognitoEventHandler({ config, onSuccess, onError }) {
34
33
  })
35
34
  );
36
35
  }
37
- const tokens = await ofetch(
36
+ const tokens = await $fetch(
38
37
  tokenURL,
39
38
  {
40
39
  method: "POST",
@@ -58,7 +57,7 @@ export function cognitoEventHandler({ config, onSuccess, onError }) {
58
57
  }
59
58
  const tokenType = tokens.token_type;
60
59
  const accessToken = tokens.access_token;
61
- const user = await ofetch(`https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
60
+ const user = await $fetch(`https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
62
61
  headers: {
63
62
  Authorization: `${tokenType} ${accessToken}`
64
63
  }
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parseURL, stringifyParsedURL } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function discordEventHandler({ config, onSuccess, onError }) {
@@ -43,7 +42,7 @@ export function discordEventHandler({ config, onSuccess, onError }) {
43
42
  }
44
43
  const parsedRedirectUrl = parseURL(redirectUrl);
45
44
  parsedRedirectUrl.search = "";
46
- const tokens = await ofetch(
45
+ const tokens = await $fetch(
47
46
  config.tokenURL,
48
47
  {
49
48
  method: "POST",
@@ -72,7 +71,7 @@ export function discordEventHandler({ config, onSuccess, onError }) {
72
71
  return onError(event, error);
73
72
  }
74
73
  const accessToken = tokens.access_token;
75
- const user = await ofetch("https://discord.com/api/users/@me", {
74
+ const user = await $fetch("https://discord.com/api/users/@me", {
76
75
  headers: {
77
76
  "user-agent": "Nuxt Auth Utils",
78
77
  "Authorization": `Bearer ${accessToken}`
@@ -5,7 +5,6 @@ import {
5
5
  getRequestURL,
6
6
  sendRedirect
7
7
  } from "h3";
8
- import { ofetch } from "ofetch";
9
8
  import { withQuery } from "ufo";
10
9
  import { defu } from "defu";
11
10
  import { useRuntimeConfig } from "#imports";
@@ -74,7 +73,7 @@ export function facebookEventHandler({
74
73
  const accessToken = tokens.access_token;
75
74
  config.fields = config.fields || ["id", "name"];
76
75
  const fields = config.fields.join(",");
77
- const user = await ofetch(
76
+ const user = await $fetch(
78
77
  `https://graph.facebook.com/v19.0/me?fields=${fields}&access_token=${accessToken}`
79
78
  );
80
79
  if (!user) {
@@ -1,5 +1,4 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
- import { ofetch } from "ofetch";
3
2
  import { withQuery } from "ufo";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
@@ -68,14 +67,14 @@ export function githubEventHandler({ config, onSuccess, onError }) {
68
67
  return onError(event, error);
69
68
  }
70
69
  const accessToken = tokens.access_token;
71
- const user = await ofetch("https://api.github.com/user", {
70
+ const user = await $fetch("https://api.github.com/user", {
72
71
  headers: {
73
72
  "User-Agent": `Github-OAuth-${config.clientId}`,
74
73
  "Authorization": `token ${accessToken}`
75
74
  }
76
75
  });
77
76
  if (!user.email && config.emailRequired) {
78
- const emails = await ofetch("https://api.github.com/user/emails", {
77
+ const emails = await $fetch("https://api.github.com/user/emails", {
79
78
  headers: {
80
79
  "User-Agent": `Github-OAuth-${config.clientId}`,
81
80
  "Authorization": `token ${accessToken}`
@@ -6,7 +6,6 @@ import {
6
6
  sendRedirect
7
7
  } from "h3";
8
8
  import { withQuery, parsePath } from "ufo";
9
- import { ofetch } from "ofetch";
10
9
  import { defu } from "defu";
11
10
  import { useRuntimeConfig } from "#imports";
12
11
  export function googleEventHandler({
@@ -52,7 +51,7 @@ export function googleEventHandler({
52
51
  client_secret: config.clientSecret,
53
52
  code
54
53
  };
55
- const tokens = await ofetch(config.tokenURL, {
54
+ const tokens = await $fetch(config.tokenURL, {
56
55
  method: "POST",
57
56
  body
58
57
  }).catch((error) => {
@@ -69,7 +68,7 @@ export function googleEventHandler({
69
68
  return onError(event, error);
70
69
  }
71
70
  const accessToken = tokens.access_token;
72
- const user = await ofetch(
71
+ const user = await $fetch(
73
72
  config.userURL,
74
73
  {
75
74
  headers: {
@@ -5,7 +5,6 @@ import {
5
5
  getRequestURL,
6
6
  sendRedirect
7
7
  } from "h3";
8
- import { ofetch } from "ofetch";
9
8
  import { withQuery, parsePath } from "ufo";
10
9
  import { defu } from "defu";
11
10
  import { useRuntimeConfig } from "#imports";
@@ -60,7 +59,7 @@ export function keycloakEventHandler({
60
59
  if (!config.scope.includes("openid")) {
61
60
  config.scope.push("openid");
62
61
  }
63
- const tokens = await ofetch(tokenURL, {
62
+ const tokens = await $fetch(tokenURL, {
64
63
  method: "POST",
65
64
  headers: {
66
65
  "Content-Type": "application/x-www-form-urlencoded"
@@ -86,7 +85,7 @@ export function keycloakEventHandler({
86
85
  return onError(event, error);
87
86
  }
88
87
  const accessToken = tokens.access_token;
89
- const user = await ofetch(
88
+ const user = await $fetch(
90
89
  `${realmURL}/protocol/openid-connect/userinfo`,
91
90
  {
92
91
  headers: {
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parseURL, stringifyParsedURL } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function linkedinEventHandler({ config, onSuccess, onError }) {
@@ -42,7 +41,7 @@ export function linkedinEventHandler({ config, onSuccess, onError }) {
42
41
  }
43
42
  const parsedRedirectUrl = parseURL(redirectUrl);
44
43
  parsedRedirectUrl.search = "";
45
- const tokens = await ofetch(
44
+ const tokens = await $fetch(
46
45
  config.tokenURL,
47
46
  {
48
47
  method: "POST",
@@ -71,7 +70,7 @@ export function linkedinEventHandler({ config, onSuccess, onError }) {
71
70
  return onError(event, error);
72
71
  }
73
72
  const accessToken = tokens.access_token;
74
- const user = await ofetch("https://api.linkedin.com/v2/userinfo", {
73
+ const user = await $fetch("https://api.linkedin.com/v2/userinfo", {
75
74
  headers: {
76
75
  "user-agent": "Nuxt Auth Utils",
77
76
  "Authorization": `Bearer ${accessToken}`
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function microsoftEventHandler({ config, onSuccess, onError }) {
@@ -40,7 +39,7 @@ export function microsoftEventHandler({ config, onSuccess, onError }) {
40
39
  data.append("client_secret", config.clientSecret);
41
40
  data.append("redirect_uri", parsePath(redirectUrl).pathname);
42
41
  data.append("code", String(code));
43
- const tokens = await ofetch(
42
+ const tokens = await $fetch(
44
43
  tokenURL,
45
44
  {
46
45
  method: "POST",
@@ -65,7 +64,7 @@ export function microsoftEventHandler({ config, onSuccess, onError }) {
65
64
  const tokenType = tokens.token_type;
66
65
  const accessToken = tokens.access_token;
67
66
  const userURL = config.userURL || "https://graph.microsoft.com/v1.0/me";
68
- const user = await ofetch(userURL, {
67
+ const user = await $fetch(userURL, {
69
68
  headers: {
70
69
  Authorization: `${tokenType} ${accessToken}`
71
70
  }
@@ -0,0 +1,47 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthPaypalConfig {
3
+ /**
4
+ * PayPal Client ID
5
+ * @default process.env.NUXT_OAUTH_PAYPAL_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * PayPal OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_PAYPAL_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * PayPal OAuth Scope
15
+ * @default []
16
+ * @see https://developer.paypal.com/docs/log-in-with-paypal/integrate/reference/#scope-attributes
17
+ * @example ['email', 'profile']
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
+ * Use PayPal sandbox environment
27
+ * @default import.meta.dev // true in development, false in production
28
+ */
29
+ sandbox?: boolean;
30
+ /**
31
+ * PayPal OAuth Authorization URL
32
+ * @default 'https://www.paypal.com/signin/authorize'
33
+ */
34
+ authorizationURL?: string;
35
+ /**
36
+ * PayPal OAuth Token URL
37
+ * @default 'https://api-m.paypal.com/v1/oauth2/token'
38
+ */
39
+ tokenURL?: string;
40
+ /**
41
+ * Extra authorization parameters to provide to the authorization URL
42
+ * @see https://developer.paypal.com/docs/log-in-with-paypal/integrate/build-button/#link-constructauthorizationendpoint
43
+ * @example { flowEntry: 'static' }
44
+ */
45
+ authorizationParams?: Record<string, string>;
46
+ }
47
+ export declare function paypalEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthPaypalConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,104 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery, parsePath } from "ufo";
3
+ import { defu } from "defu";
4
+ import { useRuntimeConfig } from "#imports";
5
+ export function paypalEventHandler({ config, onSuccess, onError }) {
6
+ return eventHandler(async (event) => {
7
+ config = defu(config, useRuntimeConfig(event).oauth?.paypal, {
8
+ sandbox: import.meta.dev,
9
+ authorizationURL: "https://www.paypal.com/signin/authorize",
10
+ tokenURL: "https://api-m.paypal.com/v1/oauth2/token",
11
+ authorizationParams: {}
12
+ });
13
+ const { code } = getQuery(event);
14
+ if (!config.clientId) {
15
+ const error = createError({
16
+ statusCode: 500,
17
+ message: "Missing NUXT_OAUTH_PAYPAL_CLIENT_ID env variables."
18
+ });
19
+ if (!onError)
20
+ throw error;
21
+ return onError(event, error);
22
+ }
23
+ let paypalAPI = "api-m.paypal.com";
24
+ if (config.sandbox) {
25
+ paypalAPI = "api-m.sandbox.paypal.com";
26
+ config.authorizationURL = "https://www.sandbox.paypal.com/signin/authorize";
27
+ config.tokenURL = `https://${paypalAPI}/v1/oauth2/token`;
28
+ }
29
+ const redirectUrl = getRequestURL(event).href;
30
+ if (!code) {
31
+ config.scope = config.scope || [];
32
+ if (!config.scope.includes("openid")) {
33
+ config.scope.push("openid");
34
+ }
35
+ if (config.emailRequired && !config.scope.includes("email")) {
36
+ config.scope.push("email");
37
+ }
38
+ return sendRedirect(
39
+ event,
40
+ withQuery(config.authorizationURL, {
41
+ response_type: "code",
42
+ client_id: config.clientId,
43
+ redirect_uri: redirectUrl,
44
+ scope: config.scope.join(" "),
45
+ flowEntry: "static",
46
+ ...config.authorizationParams
47
+ })
48
+ );
49
+ }
50
+ const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
51
+ const tokens = await $fetch(
52
+ config.tokenURL,
53
+ {
54
+ method: "POST",
55
+ headers: {
56
+ "Authorization": `Basic ${authCode}`,
57
+ "Content-Type": "application/x-www-form-urlencoded"
58
+ },
59
+ params: {
60
+ grant_type: "authorization_code",
61
+ redirect_uri: encodeURIComponent(parsePath(redirectUrl).pathname),
62
+ code
63
+ }
64
+ }
65
+ ).catch((error) => {
66
+ return { error };
67
+ });
68
+ if (tokens.error) {
69
+ const error = createError({
70
+ statusCode: 401,
71
+ message: `PayPal login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
72
+ data: tokens
73
+ });
74
+ if (!onError)
75
+ throw error;
76
+ return onError(event, error);
77
+ }
78
+ const accessToken = tokens.access_token;
79
+ const users = await $fetch(`https://${paypalAPI}/v1/identity/openidconnect/userinfo`, {
80
+ headers: {
81
+ "Authorization": `Bearer ${accessToken}`,
82
+ "Content-Type": "application/x-www-form-urlencoded"
83
+ },
84
+ params: {
85
+ schema: "openid"
86
+ }
87
+ });
88
+ const user = users;
89
+ if (!user) {
90
+ const error = createError({
91
+ statusCode: 500,
92
+ message: "Could not get PayPal user",
93
+ data: tokens
94
+ });
95
+ if (!onError)
96
+ throw error;
97
+ return onError(event, error);
98
+ }
99
+ return onSuccess(event, {
100
+ tokens,
101
+ user
102
+ });
103
+ });
104
+ }
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function spotifyEventHandler({ config, onSuccess, onError }) {
@@ -38,7 +37,7 @@ export function spotifyEventHandler({ config, onSuccess, onError }) {
38
37
  );
39
38
  }
40
39
  const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
41
- const tokens = await ofetch(
40
+ const tokens = await $fetch(
42
41
  config.tokenURL,
43
42
  {
44
43
  method: "POST",
@@ -66,7 +65,7 @@ export function spotifyEventHandler({ config, onSuccess, onError }) {
66
65
  return onError(event, error);
67
66
  }
68
67
  const accessToken = tokens.access_token;
69
- const user = await ofetch("https://api.spotify.com/v1/me", {
68
+ const user = await $fetch("https://api.spotify.com/v1/me", {
70
69
  headers: {
71
70
  Authorization: `Bearer ${accessToken}`
72
71
  }
@@ -0,0 +1,15 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthSteamConfig {
3
+ /**
4
+ * Steam API Key
5
+ * @default process.env.NUXT_OAUTH_STEAM_API_KEY
6
+ * @see https://steamcommunity.com/dev
7
+ */
8
+ apiKey?: string;
9
+ /**
10
+ * Steam Open ID OAuth Authorization URL
11
+ * @default 'https://steamcommunity.com/openid/login'
12
+ */
13
+ authorizationURL?: string;
14
+ }
15
+ export declare function steamEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthSteamConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,54 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { useRuntimeConfig } from "#imports";
5
+ export function steamEventHandler({ config, onSuccess, onError }) {
6
+ return eventHandler(async (event) => {
7
+ config = defu(config, useRuntimeConfig(event).oauth?.steam, {
8
+ authorizationURL: "https://steamcommunity.com/openid/login"
9
+ });
10
+ const query = getQuery(event);
11
+ if (!config.apiKey) {
12
+ const error = createError({
13
+ statusCode: 500,
14
+ message: "Missing NUXT_OAUTH_STEAM_API_KEY env variable."
15
+ });
16
+ if (!onError)
17
+ throw error;
18
+ return onError(event, error);
19
+ }
20
+ if (!query["openid.claimed_id"]) {
21
+ const redirectUrl = getRequestURL(event).href;
22
+ const steamOpenIdParams = {
23
+ "openid.ns": "http://specs.openid.net/auth/2.0",
24
+ "openid.mode": "checkid_setup",
25
+ "openid.return_to": redirectUrl,
26
+ "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
27
+ "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select"
28
+ };
29
+ return sendRedirect(event, withQuery(config.authorizationURL, steamOpenIdParams));
30
+ }
31
+ const validateAuth = await $fetch(withQuery(config.authorizationURL, {
32
+ ...query,
33
+ "openid.mode": "check_authentication"
34
+ }));
35
+ if (!validateAuth.includes("is_valid:true")) {
36
+ const error = createError({
37
+ statusCode: 401,
38
+ message: "Steam login failed: Unknown error"
39
+ });
40
+ if (!onError)
41
+ throw error;
42
+ return onError(event, error);
43
+ }
44
+ const steamId = query["openid.claimed_id"].split("/").pop();
45
+ const user = await $fetch(withQuery("https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/", {
46
+ key: config.apiKey,
47
+ steamids: steamId
48
+ }));
49
+ return onSuccess(event, {
50
+ user: user.response.players[0],
51
+ tokens: null
52
+ });
53
+ });
54
+ }
@@ -1,6 +1,5 @@
1
1
  import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
- import { ofetch } from "ofetch";
4
3
  import { defu } from "defu";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  export function twitchEventHandler({ config, onSuccess, onError }) {
@@ -37,7 +36,7 @@ export function twitchEventHandler({ config, onSuccess, onError }) {
37
36
  })
38
37
  );
39
38
  }
40
- const tokens = await ofetch(
39
+ const tokens = await $fetch(
41
40
  config.tokenURL,
42
41
  {
43
42
  method: "POST",
@@ -66,7 +65,7 @@ export function twitchEventHandler({ config, onSuccess, onError }) {
66
65
  return onError(event, error);
67
66
  }
68
67
  const accessToken = tokens.access_token;
69
- const users = await ofetch("https://api.twitch.tv/helix/users", {
68
+ const users = await $fetch("https://api.twitch.tv/helix/users", {
70
69
  headers: {
71
70
  "Client-ID": config.clientId,
72
71
  "Authorization": `Bearer ${accessToken}`
@@ -0,0 +1,46 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthXConfig {
3
+ /**
4
+ * X OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_X_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * X OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_X_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * X OAuth Scope
15
+ * @default []
16
+ * @see https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token
17
+ * @example ['tweet.read', 'users.read', 'offline.access'],
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * X OAuth Authorization URL
27
+ * @default 'https://twitter.com/i/oauth2/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * X OAuth Token URL
32
+ * @default 'https://api.twitter.com/2/oauth2/token'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * X OAuth User URL
37
+ * @default 'https://api.twitter.com/2/users/me'
38
+ */
39
+ userURL?: string;
40
+ /**
41
+ * Extra authorization parameters to provide to the authorization URL
42
+ * @see https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token
43
+ */
44
+ authorizationParams: Record<string, string>;
45
+ }
46
+ export declare function xEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthXConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,122 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ eventHandler,
4
+ createError,
5
+ getQuery,
6
+ getRequestURL,
7
+ sendRedirect
8
+ } from "h3";
9
+ import { withQuery, parsePath } from "ufo";
10
+ import { defu } from "defu";
11
+ import { useRuntimeConfig } from "#imports";
12
+ export function xEventHandler({
13
+ config,
14
+ onSuccess,
15
+ onError
16
+ }) {
17
+ return eventHandler(async (event) => {
18
+ config = defu(config, useRuntimeConfig(event).oauth?.x, {
19
+ authorizationURL: "https://twitter.com/i/oauth2/authorize",
20
+ tokenURL: "https://api.twitter.com/2/oauth2/token",
21
+ userURL: "https://api.twitter.com/2/users/me",
22
+ authorizationParams: {
23
+ state: randomUUID(),
24
+ code_challenge: randomUUID()
25
+ }
26
+ });
27
+ const { code } = getQuery(event);
28
+ if (!config.clientId) {
29
+ const error = createError({
30
+ statusCode: 500,
31
+ message: "Missing NUXT_OAUTH_X_CLIENT_ID env variables."
32
+ });
33
+ if (!onError)
34
+ throw error;
35
+ return onError(event, error);
36
+ }
37
+ const redirectUrl = getRequestURL(event).href;
38
+ if (!code) {
39
+ config.scope = config.scope || ["tweet.read", "users.read", "offline.access"];
40
+ return sendRedirect(
41
+ event,
42
+ withQuery(config.authorizationURL, {
43
+ response_type: "code",
44
+ client_id: config.clientId,
45
+ code_challenge_method: "plain",
46
+ redirect_uri: redirectUrl,
47
+ scope: config.scope.join(" "),
48
+ ...config.authorizationParams
49
+ })
50
+ );
51
+ }
52
+ const params = {
53
+ grant_type: "authorization_code",
54
+ code_verifier: config.authorizationParams.code_challenge,
55
+ redirect_uri: parsePath(redirectUrl).pathname,
56
+ code
57
+ };
58
+ const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
59
+ const tokens = await $fetch(config.tokenURL, {
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/x-www-form-urlencoded",
63
+ "Authorization": `Basic ${authCode}`
64
+ },
65
+ params
66
+ }).catch((error) => {
67
+ return { error };
68
+ });
69
+ if (tokens.error) {
70
+ const error = createError({
71
+ statusCode: 401,
72
+ message: `X login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
73
+ data: tokens
74
+ });
75
+ if (!onError)
76
+ throw error;
77
+ return onError(event, error);
78
+ }
79
+ const accessToken = tokens.access_token;
80
+ const user = await $fetch(
81
+ config.userURL,
82
+ {
83
+ headers: {
84
+ Authorization: `Bearer ${accessToken}`
85
+ },
86
+ query: {
87
+ "user.fields": "description,id,name,profile_image_url,username,verified,verified_type"
88
+ }
89
+ }
90
+ ).catch((error) => {
91
+ return error;
92
+ });
93
+ if (config.emailRequired) {
94
+ const emailData = await $fetch("https://api.twitter.com/1.1/account/verify_credentials.json", {
95
+ headers: {
96
+ Authorization: `Bearer ${accessToken}`
97
+ },
98
+ query: {
99
+ include_email: "true",
100
+ skip_status: "true"
101
+ }
102
+ }).catch((error) => {
103
+ return error;
104
+ });
105
+ if (emailData && emailData.email) {
106
+ user.email = emailData.email;
107
+ } else {
108
+ const error = createError({
109
+ statusCode: 401,
110
+ message: "Twitter login failed: no user email found"
111
+ });
112
+ if (!onError)
113
+ throw error;
114
+ return onError(event, error);
115
+ }
116
+ }
117
+ return onSuccess(event, {
118
+ tokens,
119
+ user: user?.data
120
+ });
121
+ });
122
+ }
@@ -0,0 +1,26 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthXSUAAConfig {
3
+ /**
4
+ * XSUAA OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_XSUAA_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * XSUAA OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_XSUAA_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * XSUAA OAuth Issuer
15
+ * @default process.env.NUXT_OAUTH_XSUAA_DOMAIN
16
+ */
17
+ domain?: string;
18
+ /**
19
+ * XSUAA OAuth Scope
20
+ * @default []
21
+ * @see https://sap.github.io/cloud-sdk/docs/java/guides/cloud-foundry-xsuaa-service
22
+ * @example ['openid']
23
+ */
24
+ scope?: string[];
25
+ }
26
+ export declare function xsuaaEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthXSUAAConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,73 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery, parsePath } from "ufo";
3
+ import { defu } from "defu";
4
+ import { useRuntimeConfig } from "#imports";
5
+ export function xsuaaEventHandler({ config, onSuccess, onError }) {
6
+ return eventHandler(async (event) => {
7
+ config = defu(config, useRuntimeConfig(event).oauth?.xsuaa);
8
+ const { code } = getQuery(event);
9
+ if (!config.clientId || !config.clientSecret || !config.domain) {
10
+ const error = createError({
11
+ statusCode: 500,
12
+ message: "Missing NUXT_OAUTH_XSUAA_CLIENT_ID or NUXT_OAUTH_XSUAA_CLIENT_SECRET or NUXT_OAUTH_XSUAA_DOMAIN env variables."
13
+ });
14
+ if (!onError)
15
+ throw error;
16
+ return onError(event, error);
17
+ }
18
+ const authorizationURL = `https://${config.domain}/oauth/authorize`;
19
+ const tokenURL = `https://${config.domain}/oauth/token`;
20
+ const redirectUrl = getRequestURL(event).href;
21
+ if (!code) {
22
+ config.scope = config.scope || [];
23
+ return sendRedirect(
24
+ event,
25
+ withQuery(authorizationURL, {
26
+ response_type: "code",
27
+ client_id: config.clientId,
28
+ redirect_uri: redirectUrl,
29
+ scope: config.scope.join(" ")
30
+ })
31
+ );
32
+ }
33
+ const tokens = await $fetch(
34
+ tokenURL,
35
+ {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/x-www-form-urlencoded"
39
+ },
40
+ body: new URLSearchParams({
41
+ grant_type: "authorization_code",
42
+ client_id: config.clientId,
43
+ client_secret: config.clientSecret,
44
+ redirect_uri: parsePath(redirectUrl).pathname,
45
+ code: `${code}`
46
+ })
47
+ }
48
+ ).catch((error) => {
49
+ return { error };
50
+ });
51
+ if (tokens.error) {
52
+ const error = createError({
53
+ statusCode: 401,
54
+ message: `XSUAA login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
55
+ data: tokens
56
+ });
57
+ if (!onError)
58
+ throw error;
59
+ return onError(event, error);
60
+ }
61
+ const tokenType = tokens.token_type;
62
+ const accessToken = tokens.access_token;
63
+ const user = await $fetch(`https://${config.domain}/userinfo`, {
64
+ headers: {
65
+ Authorization: `${tokenType} ${accessToken}`
66
+ }
67
+ });
68
+ return onSuccess(event, {
69
+ tokens,
70
+ user
71
+ });
72
+ });
73
+ }
@@ -0,0 +1,41 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthYandexConfig {
3
+ /**
4
+ * Yandex OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_YANDEX_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Yandex OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_YANDEX_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Yandex OAuth Scope
15
+ * @default []
16
+ * @see https://yandex.ru/dev/id/doc/en/codes/code-url#optional
17
+ * @example ["login:avatar", "login:birthday", "login:email", "login:info", "login:default_phone"]
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user, adds the ['login:email'] scope if not present
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * Yandex OAuth Authorization URL
27
+ * @default 'https://oauth.yandex.ru/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * Yandex OAuth Token URL
32
+ * @default 'https://oauth.yandex.ru/token'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * Yandex OAuth User URL
37
+ * @default 'https://login.yandex.ru/info'
38
+ */
39
+ userURL?: string;
40
+ }
41
+ export declare function yandexEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthYandexConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,84 @@
1
+ import {
2
+ eventHandler,
3
+ createError,
4
+ getQuery,
5
+ getRequestURL,
6
+ sendRedirect
7
+ } from "h3";
8
+ import { withQuery, parseURL, stringifyParsedURL } from "ufo";
9
+ import { defu } from "defu";
10
+ import { useRuntimeConfig } from "#imports";
11
+ export function yandexEventHandler({
12
+ config,
13
+ onSuccess,
14
+ onError
15
+ }) {
16
+ return eventHandler(async (event) => {
17
+ config = defu(config, useRuntimeConfig(event).oauth?.yandex, {
18
+ authorizationURL: "https://oauth.yandex.ru/authorize",
19
+ tokenURL: "https://oauth.yandex.ru/token",
20
+ userURL: "https://login.yandex.ru/info"
21
+ });
22
+ const { code } = getQuery(event);
23
+ if (!config.clientId || !config.clientSecret) {
24
+ const error = createError({
25
+ statusCode: 500,
26
+ message: "Missing NUXT_OAUTH_YANDEX_CLIENT_ID or NUXT_OAUTH_YANDEX_CLIENT_SECRET env variables."
27
+ });
28
+ if (!onError)
29
+ throw error;
30
+ return onError(event, error);
31
+ }
32
+ const redirectUrl = getRequestURL(event).href;
33
+ if (!code) {
34
+ config.scope = config.scope || [];
35
+ if (config.emailRequired && !config.scope.includes("login:email")) {
36
+ config.scope.push("login:email");
37
+ }
38
+ return sendRedirect(
39
+ event,
40
+ withQuery(config.authorizationURL, {
41
+ response_type: "code",
42
+ client_id: config.clientId,
43
+ redirect_uri: redirectUrl,
44
+ scope: config.scope.join(" ")
45
+ })
46
+ );
47
+ }
48
+ const tokens = await $fetch(config.tokenURL, {
49
+ method: "POST",
50
+ headers: {
51
+ "Content-Type": "application/x-www-form-urlencoded"
52
+ },
53
+ body: new URLSearchParams({
54
+ client_id: config.clientId,
55
+ client_secret: config.clientSecret,
56
+ grant_type: "authorization_code",
57
+ redirect_uri: stringifyParsedURL(parseURL(redirectUrl)),
58
+ code
59
+ }).toString()
60
+ }).catch((error) => {
61
+ return { error };
62
+ });
63
+ if (tokens.error) {
64
+ const error = createError({
65
+ statusCode: 401,
66
+ message: `Yandex login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
67
+ data: tokens
68
+ });
69
+ if (!onError)
70
+ throw error;
71
+ return onError(event, error);
72
+ }
73
+ const accessToken = tokens.access_token;
74
+ const user = await $fetch(config.userURL, {
75
+ headers: {
76
+ Authorization: `OAuth ${accessToken}`
77
+ }
78
+ });
79
+ return onSuccess(event, {
80
+ tokens,
81
+ user
82
+ });
83
+ });
84
+ }
@@ -10,6 +10,11 @@ import { keycloakEventHandler } from '../lib/oauth/keycloak.js';
10
10
  import { linkedinEventHandler } from '../lib/oauth/linkedin.js';
11
11
  import { cognitoEventHandler } from '../lib/oauth/cognito.js';
12
12
  import { facebookEventHandler } from '../lib/oauth/facebook.js';
13
+ import { paypalEventHandler } from '../lib/oauth/paypal.js';
14
+ import { steamEventHandler } from '../lib/oauth/steam.js';
15
+ import { xEventHandler } from '../lib/oauth/x.js';
16
+ import { xsuaaEventHandler } from '../lib/oauth/xsuaa.js';
17
+ import { yandexEventHandler } from '../lib/oauth/yandex.js';
13
18
  export declare const oauth: {
14
19
  githubEventHandler: typeof githubEventHandler;
15
20
  spotifyEventHandler: typeof spotifyEventHandler;
@@ -23,4 +28,9 @@ export declare const oauth: {
23
28
  linkedinEventHandler: typeof linkedinEventHandler;
24
29
  cognitoEventHandler: typeof cognitoEventHandler;
25
30
  facebookEventHandler: typeof facebookEventHandler;
31
+ paypalEventHandler: typeof paypalEventHandler;
32
+ steamEventHandler: typeof steamEventHandler;
33
+ xEventHandler: typeof xEventHandler;
34
+ xsuaaEventHandler: typeof xsuaaEventHandler;
35
+ yandexEventHandler: typeof yandexEventHandler;
26
36
  };
@@ -10,6 +10,11 @@ import { keycloakEventHandler } from "../lib/oauth/keycloak.js";
10
10
  import { linkedinEventHandler } from "../lib/oauth/linkedin.js";
11
11
  import { cognitoEventHandler } from "../lib/oauth/cognito.js";
12
12
  import { facebookEventHandler } from "../lib/oauth/facebook.js";
13
+ import { paypalEventHandler } from "../lib/oauth/paypal.js";
14
+ import { steamEventHandler } from "../lib/oauth/steam.js";
15
+ import { xEventHandler } from "../lib/oauth/x.js";
16
+ import { xsuaaEventHandler } from "../lib/oauth/xsuaa.js";
17
+ import { yandexEventHandler } from "../lib/oauth/yandex.js";
13
18
  export const oauth = {
14
19
  githubEventHandler,
15
20
  spotifyEventHandler,
@@ -22,5 +27,10 @@ export const oauth = {
22
27
  keycloakEventHandler,
23
28
  linkedinEventHandler,
24
29
  cognitoEventHandler,
25
- facebookEventHandler
30
+ facebookEventHandler,
31
+ paypalEventHandler,
32
+ steamEventHandler,
33
+ xEventHandler,
34
+ xsuaaEventHandler,
35
+ yandexEventHandler
26
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Minimalist Auth module for Nuxt with SSR",
5
5
  "repository": "Atinux/nuxt-auth-utils",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
26
26
  "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
27
27
  "lint": "eslint .",
28
+ "lint:fix": "eslint . --fix",
28
29
  "test": "vitest run",
29
30
  "test:types": "vue-tsc --noEmit",
30
31
  "test:types:playground": "cd playground && vue-tsc --noEmit",