nuxt-auth-utils 0.0.4 → 0.0.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
@@ -7,14 +7,15 @@
7
7
 
8
8
  Minimalist Authentication module for Nuxt exposing Vue composables and server utils.
9
9
 
10
- - [✨  Release Notes](/CHANGELOG.md)
10
+ - [Release Notes](/CHANGELOG.md)
11
+ - [Demo](https://github.com/atinux/nuxt-todos-edge)
11
12
  <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/nuxt-auth-utils?file=playground%2Fapp.vue) -->
12
13
  <!-- - [📖 &nbsp;Documentation](https://example.com) -->
13
14
 
14
15
  ## Features
15
16
 
16
17
  - Secured & sealed cookies sessions
17
- - OAuth Providers
18
+ - [OAuth Providers](#supported-oauth-providers)
18
19
 
19
20
  ## Requirements
20
21
 
@@ -144,9 +145,15 @@ It can also be set using environment variables:
144
145
  - `NUXT_OAUTH_<PROVIDER>_CLIENT_ID`
145
146
  - `NUXT_OAUTH_<PROVIDER>_CLIENT_SECRET`
146
147
 
147
- Supported providers:
148
+ #### Supported OAuth Providers
149
+
148
150
  - GitHub
149
151
  - Spotify
152
+ - Google
153
+ - Twitch
154
+ - Auth0
155
+
156
+ You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
150
157
 
151
158
  ### Example
152
159
 
@@ -154,6 +161,9 @@ Example: `~/server/routes/auth/github.get.ts`
154
161
 
155
162
  ```ts
156
163
  export default oauth.githubEventHandler({
164
+ config: {
165
+ emailRequired: true
166
+ },
157
167
  async onSuccess(event, { user, tokens }) {
158
168
  await setUserSession(event, {
159
169
  user: {
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "auth-core",
2
+ "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.0.4"
4
+ "version": "0.0.5"
5
5
  }
package/dist/module.mjs CHANGED
@@ -4,20 +4,18 @@ import { defu } from 'defu';
4
4
 
5
5
  const module = defineNuxtModule({
6
6
  meta: {
7
- name: "auth-core",
7
+ name: "auth-utils",
8
8
  configKey: "auth"
9
9
  },
10
10
  // Default configuration options of the Nuxt module
11
11
  defaults: {},
12
12
  setup(options, nuxt) {
13
13
  const resolver = createResolver(import.meta.url);
14
- if (!process.env.NUXT_SESSION_PASSWORD) {
14
+ if (!process.env.NUXT_SESSION_PASSWORD && !nuxt.options._prepare) {
15
15
  const randomPassword = sha256(`${Date.now()}${Math.random()}`).slice(0, 32);
16
16
  process.env.NUXT_SESSION_PASSWORD = randomPassword;
17
- if (!nuxt.options._prepare) {
18
- console.warn("No session password set, using a random password, please set NUXT_SESSION_PASSWORD in your .env file with at least 32 chars");
19
- console.log(`NUXT_SESSION_PASSWORD=${randomPassword}`);
20
- }
17
+ console.warn("No session password set, using a random password, please set NUXT_SESSION_PASSWORD in your .env file with at least 32 chars");
18
+ console.log(`NUXT_SESSION_PASSWORD=${randomPassword}`);
21
19
  }
22
20
  nuxt.options.alias["#auth-utils"] = resolver.resolve("./runtime/types/auth-utils-session");
23
21
  addImportsDir(resolver.resolve("./runtime/composables"));
@@ -65,6 +63,19 @@ const module = defineNuxtModule({
65
63
  clientId: "",
66
64
  clientSecret: ""
67
65
  });
66
+ runtimeConfig.oauth.google = defu(runtimeConfig.oauth.google, {
67
+ clientId: "",
68
+ clientSecret: ""
69
+ });
70
+ runtimeConfig.oauth.twitch = defu(runtimeConfig.oauth.twitch, {
71
+ clientId: "",
72
+ clientSecret: ""
73
+ });
74
+ runtimeConfig.oauth.auth0 = defu(runtimeConfig.oauth.auth0, {
75
+ clientId: "",
76
+ clientSecret: "",
77
+ domain: ""
78
+ });
68
79
  }
69
80
  });
70
81
 
@@ -0,0 +1,45 @@
1
+ import type { H3Event, H3Error } from 'h3';
2
+ export interface OAuthAuth0Config {
3
+ /**
4
+ * Auth0 OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_AUTH0_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Auth0 OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_AUTH0_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Auth0 OAuth Issuer
15
+ * @default process.env.NUXT_OAUTH_AUTH0_DOMAIN
16
+ */
17
+ domain?: string;
18
+ /**
19
+ * Auth0 OAuth Audience
20
+ * @default process.env.NUXT_OAUTH_AUTH0_AUDIENCE
21
+ */
22
+ audience?: string;
23
+ /**
24
+ * Auth0 OAuth Scope
25
+ * @default []
26
+ * @see https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes
27
+ * @example ['openid']
28
+ */
29
+ scope?: string[];
30
+ /**
31
+ * Require email from user, adds the ['email'] scope if not present
32
+ * @default false
33
+ */
34
+ emailRequired?: boolean;
35
+ }
36
+ interface OAuthConfig {
37
+ config?: OAuthAuth0Config;
38
+ onSuccess: (event: H3Event, result: {
39
+ user: any;
40
+ tokens: any;
41
+ }) => Promise<void> | void;
42
+ onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
43
+ }
44
+ export declare function auth0EventHandler({ config, onSuccess, onError }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
45
+ export {};
@@ -0,0 +1,78 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery, parsePath } from "ufo";
3
+ import { ofetch } from "ofetch";
4
+ import { defu } from "defu";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function auth0EventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.auth0);
9
+ const { code } = getQuery(event);
10
+ if (!config.clientId || !config.clientSecret || !config.domain) {
11
+ const error = createError({
12
+ statusCode: 500,
13
+ message: "Missing NUXT_OAUTH_AUTH0_CLIENT_ID or NUXT_OAUTH_AUTH0_CLIENT_SECRET or NUXT_OAUTH_AUTH0_DOMAIN env variables."
14
+ });
15
+ if (!onError)
16
+ throw error;
17
+ return onError(event, error);
18
+ }
19
+ const authorizationURL = `https://${config.domain}/authorize`;
20
+ const tokenURL = `https://${config.domain}/oauth/token`;
21
+ const redirectUrl = getRequestURL(event).href;
22
+ if (!code) {
23
+ config.scope = config.scope || ["openid", "offline_access"];
24
+ if (config.emailRequired && !config.scope.includes("email")) {
25
+ config.scope.push("email");
26
+ }
27
+ return sendRedirect(
28
+ event,
29
+ withQuery(authorizationURL, {
30
+ response_type: "code",
31
+ client_id: config.clientId,
32
+ redirect_uri: redirectUrl,
33
+ scope: config.scope.join(" "),
34
+ audience: config.audience || ""
35
+ })
36
+ );
37
+ }
38
+ const tokens = await ofetch(
39
+ tokenURL,
40
+ {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json"
44
+ },
45
+ body: {
46
+ grant_type: "authorization_code",
47
+ client_id: config.clientId,
48
+ client_secret: config.clientSecret,
49
+ redirect_uri: parsePath(redirectUrl).pathname,
50
+ code
51
+ }
52
+ }
53
+ ).catch((error) => {
54
+ return { error };
55
+ });
56
+ if (tokens.error) {
57
+ const error = createError({
58
+ statusCode: 401,
59
+ message: `Auth0 login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
60
+ data: tokens
61
+ });
62
+ if (!onError)
63
+ throw error;
64
+ return onError(event, error);
65
+ }
66
+ const tokenType = tokens.token_type;
67
+ const accessToken = tokens.access_token;
68
+ const user = await ofetch(`https://${config.domain}/userinfo`, {
69
+ headers: {
70
+ Authorization: `${tokenType} ${accessToken}`
71
+ }
72
+ });
73
+ return onSuccess(event, {
74
+ tokens,
75
+ user
76
+ });
77
+ });
78
+ }
@@ -0,0 +1,45 @@
1
+ import type { H3Event, H3Error } from 'h3';
2
+ export interface OAuthGoogleConfig {
3
+ /**
4
+ * Google OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_GOOGLE_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Google OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_GOOGLE_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Google OAuth Scope
15
+ * @default []
16
+ * @see https://developers.google.com/identity/protocols/oauth2/scopes#google-sign-in
17
+ * @example ['email', 'openid', 'profile']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Google OAuth Authorization URL
22
+ * @default 'https://accounts.google.com/o/oauth2/v2/auth'
23
+ */
24
+ authorizationURL?: string;
25
+ /**
26
+ * Google OAuth Token URL
27
+ * @default 'https://oauth2.googleapis.com/token'
28
+ */
29
+ tokenURL?: string;
30
+ /**
31
+ * Redirect URL post authenticating via google
32
+ * @default '/auth/google'
33
+ */
34
+ redirectUrl: '/auth/google';
35
+ }
36
+ interface OAuthConfig {
37
+ config?: OAuthGoogleConfig;
38
+ onSuccess: (event: H3Event, result: {
39
+ user: any;
40
+ tokens: any;
41
+ }) => Promise<void> | void;
42
+ onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
43
+ }
44
+ export declare function googleEventHandler({ config, onSuccess, onError, }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
45
+ export {};
@@ -0,0 +1,82 @@
1
+ import {
2
+ eventHandler,
3
+ createError,
4
+ getQuery,
5
+ getRequestURL,
6
+ sendRedirect
7
+ } from "h3";
8
+ import { withQuery, parsePath } from "ufo";
9
+ import { ofetch } from "ofetch";
10
+ import { defu } from "defu";
11
+ import { useRuntimeConfig } from "#imports";
12
+ export function googleEventHandler({
13
+ config,
14
+ onSuccess,
15
+ onError
16
+ }) {
17
+ return eventHandler(async (event) => {
18
+ config = defu(config, useRuntimeConfig(event).oauth?.google, {
19
+ authorizationURL: "https://accounts.google.com/o/oauth2/v2/auth",
20
+ tokenURL: "https://oauth2.googleapis.com/token"
21
+ });
22
+ const { code } = getQuery(event);
23
+ if (!config.clientId) {
24
+ const error = createError({
25
+ statusCode: 500,
26
+ message: "Missing NUXT_OAUTH_GOOGLE_CLIENT_ID 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 || ["email", "profile"];
35
+ return sendRedirect(
36
+ event,
37
+ withQuery(config.authorizationURL, {
38
+ response_type: "code",
39
+ client_id: config.clientId,
40
+ redirect_uri: redirectUrl,
41
+ scope: config.scope.join(" ")
42
+ })
43
+ );
44
+ }
45
+ const body = {
46
+ grant_type: "authorization_code",
47
+ redirect_uri: parsePath(redirectUrl).pathname,
48
+ client_id: config.clientId,
49
+ client_secret: config.clientSecret,
50
+ code
51
+ };
52
+ const tokens = await ofetch(config.tokenURL, {
53
+ method: "POST",
54
+ body
55
+ }).catch((error) => {
56
+ return { error };
57
+ });
58
+ if (tokens.error) {
59
+ const error = createError({
60
+ statusCode: 401,
61
+ message: `Google login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
62
+ data: tokens
63
+ });
64
+ if (!onError)
65
+ throw error;
66
+ return onError(event, error);
67
+ }
68
+ const accessToken = tokens.access_token;
69
+ const user = await ofetch(
70
+ "https://www.googleapis.com/oauth2/v3/userinfo",
71
+ {
72
+ headers: {
73
+ Authorization: `Bearer ${accessToken}`
74
+ }
75
+ }
76
+ );
77
+ return onSuccess(event, {
78
+ tokens,
79
+ user
80
+ });
81
+ });
82
+ }
@@ -0,0 +1,45 @@
1
+ import type { H3Event, H3Error } from 'h3';
2
+ export interface OAuthTwitchConfig {
3
+ /**
4
+ * Twitch Client ID
5
+ * @default process.env.NUXT_OAUTH_TWITCH_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Twitch OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_TWITCH_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Twitch OAuth Scope
15
+ * @default []
16
+ * @see https://dev.twitch.tv/docs/authentication/scopes
17
+ * @example ['user:read:email']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user, adds the ['user:read:email'] scope if not present
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * Twitch OAuth Authorization URL
27
+ * @default 'https://id.twitch.tv/oauth2/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * Twitch OAuth Token URL
32
+ * @default 'https://id.twitch.tv/oauth2/token'
33
+ */
34
+ tokenURL?: string;
35
+ }
36
+ interface OAuthConfig {
37
+ config?: OAuthTwitchConfig;
38
+ onSuccess: (event: H3Event, result: {
39
+ user: any;
40
+ tokens: any;
41
+ }) => Promise<void> | void;
42
+ onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
43
+ }
44
+ export declare function twitchEventHandler({ config, onSuccess, onError }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
45
+ export {};
@@ -0,0 +1,89 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery, parsePath } from "ufo";
3
+ import { ofetch } from "ofetch";
4
+ import { defu } from "defu";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function twitchEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.twitch, {
9
+ authorizationURL: "https://id.twitch.tv/oauth2/authorize",
10
+ tokenURL: "https://id.twitch.tv/oauth2/token"
11
+ });
12
+ const { code } = getQuery(event);
13
+ if (!config.clientId) {
14
+ const error = createError({
15
+ statusCode: 500,
16
+ message: "Missing NUXT_OAUTH_TWITCH_CLIENT_ID env variables."
17
+ });
18
+ if (!onError)
19
+ throw error;
20
+ return onError(event, error);
21
+ }
22
+ const redirectUrl = getRequestURL(event).href;
23
+ if (!code) {
24
+ config.scope = config.scope || [];
25
+ if (config.emailRequired && !config.scope.includes("user:read:email")) {
26
+ config.scope.push("user:read:email");
27
+ }
28
+ return sendRedirect(
29
+ event,
30
+ withQuery(config.authorizationURL, {
31
+ response_type: "code",
32
+ client_id: config.clientId,
33
+ redirect_uri: redirectUrl,
34
+ scope: config.scope.join("%20")
35
+ })
36
+ );
37
+ }
38
+ const tokens = await ofetch(
39
+ config.tokenURL,
40
+ {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/x-www-form-urlencoded"
44
+ },
45
+ params: {
46
+ grant_type: "authorization_code",
47
+ redirect_uri: parsePath(redirectUrl).pathname,
48
+ client_id: config.clientId,
49
+ client_secret: config.clientSecret,
50
+ code
51
+ }
52
+ }
53
+ ).catch((error) => {
54
+ return { error };
55
+ });
56
+ if (tokens.error) {
57
+ const error = createError({
58
+ statusCode: 401,
59
+ message: `Twitch login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
60
+ data: tokens
61
+ });
62
+ if (!onError)
63
+ throw error;
64
+ return onError(event, error);
65
+ }
66
+ const accessToken = tokens.access_token;
67
+ const users = await ofetch("https://api.twitch.tv/helix/users", {
68
+ headers: {
69
+ "Client-ID": config.clientId,
70
+ Authorization: `Bearer ${accessToken}`
71
+ }
72
+ });
73
+ const user = users.data?.[0];
74
+ if (!user) {
75
+ const error = createError({
76
+ statusCode: 500,
77
+ message: "Could not get Twitch user",
78
+ data: tokens
79
+ });
80
+ if (!onError)
81
+ throw error;
82
+ return onError(event, error);
83
+ }
84
+ return onSuccess(event, {
85
+ tokens,
86
+ user
87
+ });
88
+ });
89
+ }
@@ -1,6 +1,12 @@
1
1
  import { githubEventHandler } from '../lib/oauth/github';
2
+ import { googleEventHandler } from '../lib/oauth/google';
2
3
  import { spotifyEventHandler } from '../lib/oauth/spotify';
4
+ import { twitchEventHandler } from '../lib/oauth/twitch';
5
+ import { auth0EventHandler } from '../lib/oauth/auth0';
3
6
  export declare const oauth: {
4
7
  githubEventHandler: typeof githubEventHandler;
5
8
  spotifyEventHandler: typeof spotifyEventHandler;
9
+ googleEventHandler: typeof googleEventHandler;
10
+ twitchEventHandler: typeof twitchEventHandler;
11
+ auth0EventHandler: typeof auth0EventHandler;
6
12
  };
@@ -1,6 +1,12 @@
1
1
  import { githubEventHandler } from "../lib/oauth/github.mjs";
2
+ import { googleEventHandler } from "../lib/oauth/google.mjs";
2
3
  import { spotifyEventHandler } from "../lib/oauth/spotify.mjs";
4
+ import { twitchEventHandler } from "../lib/oauth/twitch.mjs";
5
+ import { auth0EventHandler } from "../lib/oauth/auth0.mjs";
3
6
  export const oauth = {
4
7
  githubEventHandler,
5
- spotifyEventHandler
8
+ spotifyEventHandler,
9
+ googleEventHandler,
10
+ twitchEventHandler,
11
+ auth0EventHandler
6
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Minimalist Auth module for Nuxt with SSR",
5
5
  "repository": "Atinux/nuxt-auth-utils",
6
6
  "license": "MIT",
@@ -32,6 +32,7 @@
32
32
  "dependencies": {
33
33
  "@nuxt/kit": "^3.8.1",
34
34
  "defu": "^6.1.3",
35
+ "ofetch": "^1.3.3",
35
36
  "ohash": "^1.1.3"
36
37
  },
37
38
  "devDependencies": {