nuxt-auth-utils 0.0.12 → 0.0.14
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 +5 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -0
- package/dist/runtime/server/api/session.get.mjs +1 -1
- package/dist/runtime/server/lib/oauth/keycloak.d.ts +32 -0
- package/dist/runtime/server/lib/oauth/keycloak.mjs +114 -0
- package/dist/runtime/server/utils/oauth.d.ts +2 -0
- package/dist/runtime/server/utils/oauth.mjs +2 -0
- package/dist/runtime/server/utils/session.d.ts +1 -1
- package/dist/types.d.mts +2 -2
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ It can also be set using environment variables:
|
|
|
154
154
|
- Discord
|
|
155
155
|
- GitHub
|
|
156
156
|
- Google
|
|
157
|
+
- Keycloak
|
|
157
158
|
- LinkedIn
|
|
158
159
|
- Microsoft
|
|
159
160
|
- Spotify
|
|
@@ -195,12 +196,15 @@ We leverage hooks to let you extend the session data with your own data or to lo
|
|
|
195
196
|
```ts
|
|
196
197
|
// server/plugins/session.ts
|
|
197
198
|
export default defineNitroPlugin(() => {
|
|
198
|
-
|
|
199
|
+
// Called when the session is fetched during SSR for the Vue composable (/api/_auth/session)
|
|
200
|
+
// Or when we call useUserSession().fetch()
|
|
201
|
+
sessionHooks.hook('fetch', async (session, event) => {
|
|
199
202
|
// extend User Session by calling your database
|
|
200
203
|
// or
|
|
201
204
|
// throw createError({ ... }) if session is invalid for example
|
|
202
205
|
})
|
|
203
206
|
|
|
207
|
+
// Called when we call useServerSession().clear() or clearUserSession(event)
|
|
204
208
|
sessionHooks.hook('clear', async (session, event) => {
|
|
205
209
|
// Log that user logged out
|
|
206
210
|
})
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -98,6 +98,12 @@ const module = defineNuxtModule({
|
|
|
98
98
|
clientId: "",
|
|
99
99
|
clientSecret: ""
|
|
100
100
|
});
|
|
101
|
+
runtimeConfig.oauth.keycloak = defu(runtimeConfig.oauth.keycloak, {
|
|
102
|
+
clientId: "",
|
|
103
|
+
clientSecret: "",
|
|
104
|
+
serverUrl: "",
|
|
105
|
+
realm: ""
|
|
106
|
+
});
|
|
101
107
|
runtimeConfig.oauth.linkedin = defu(runtimeConfig.oauth.linkedin, {
|
|
102
108
|
clientId: "",
|
|
103
109
|
clientSecret: ""
|
|
@@ -2,6 +2,6 @@ import { eventHandler } from "h3";
|
|
|
2
2
|
import { requireUserSession, sessionHooks } from "../utils/session.mjs";
|
|
3
3
|
export default eventHandler(async (event) => {
|
|
4
4
|
const session = await requireUserSession(event);
|
|
5
|
-
await sessionHooks.callHookParallel("
|
|
5
|
+
await sessionHooks.callHookParallel("fetch", session, event);
|
|
6
6
|
return session;
|
|
7
7
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthKeycloakConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Keycloak OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_KEYCLOAK_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Keycloak OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_KEYCLOAK_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Keycloak OAuth Server URL
|
|
15
|
+
* @example http://192.168.1.10:8080/auth
|
|
16
|
+
* @default process.env.NUXT_OAUTH_KEYCLOAK_SERVER_URL
|
|
17
|
+
*/
|
|
18
|
+
serverUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Keycloak OAuth Realm
|
|
21
|
+
* @default process.env.NUXT_OAUTH_KEYCLOAK_REALM
|
|
22
|
+
*/
|
|
23
|
+
realm?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Keycloak OAuth Scope
|
|
26
|
+
* @default []
|
|
27
|
+
* @see https://www.keycloak.org/docs/latest/authorization_services/
|
|
28
|
+
* @example ['openid']
|
|
29
|
+
*/
|
|
30
|
+
scope?: string[];
|
|
31
|
+
}
|
|
32
|
+
export declare function keycloakEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthKeycloakConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
eventHandler,
|
|
3
|
+
createError,
|
|
4
|
+
getQuery,
|
|
5
|
+
getRequestURL,
|
|
6
|
+
sendRedirect
|
|
7
|
+
} from "h3";
|
|
8
|
+
import { ofetch } from "ofetch";
|
|
9
|
+
import { withQuery, parsePath } from "ufo";
|
|
10
|
+
import { defu } from "defu";
|
|
11
|
+
import { useRuntimeConfig } from "#imports";
|
|
12
|
+
export function keycloakEventHandler({
|
|
13
|
+
config,
|
|
14
|
+
onSuccess,
|
|
15
|
+
onError
|
|
16
|
+
}) {
|
|
17
|
+
return eventHandler(async (event) => {
|
|
18
|
+
config = defu(
|
|
19
|
+
config,
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
useRuntimeConfig(event).oauth?.keycloak
|
|
22
|
+
);
|
|
23
|
+
const query = getQuery(event);
|
|
24
|
+
const { code } = query;
|
|
25
|
+
if (query.error) {
|
|
26
|
+
const error = createError({
|
|
27
|
+
statusCode: 401,
|
|
28
|
+
message: `Keycloak login failed: ${query.error || "Unknown error"}`,
|
|
29
|
+
data: query
|
|
30
|
+
});
|
|
31
|
+
if (!onError)
|
|
32
|
+
throw error;
|
|
33
|
+
return onError(event, error);
|
|
34
|
+
}
|
|
35
|
+
if (!config.clientId || !config.clientSecret || !config.serverUrl || !config.realm) {
|
|
36
|
+
const error = createError({
|
|
37
|
+
statusCode: 500,
|
|
38
|
+
message: "Missing NUXT_OAUTH_KEYCLOAK_CLIENT_ID or NUXT_OAUTH_KEYCLOAK_CLIENT_SECRET or NUXT_OAUTH_KEYCLOAK_SERVER_URL or NUXT_OAUTH_KEYCLOAK_REALM env variables."
|
|
39
|
+
});
|
|
40
|
+
if (!onError)
|
|
41
|
+
throw error;
|
|
42
|
+
return onError(event, error);
|
|
43
|
+
}
|
|
44
|
+
const realmURL = `${config.serverUrl}/realms/${config.realm}`;
|
|
45
|
+
const authorizationURL = `${realmURL}/protocol/openid-connect/auth`;
|
|
46
|
+
const tokenURL = `${realmURL}/protocol/openid-connect/token`;
|
|
47
|
+
const redirectUrl = getRequestURL(event).href;
|
|
48
|
+
if (!code) {
|
|
49
|
+
config.scope = config.scope || ["openid"];
|
|
50
|
+
return sendRedirect(
|
|
51
|
+
event,
|
|
52
|
+
withQuery(authorizationURL, {
|
|
53
|
+
client_id: config.clientId,
|
|
54
|
+
redirect_uri: redirectUrl,
|
|
55
|
+
scope: config.scope.join(" "),
|
|
56
|
+
response_type: "code"
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
config.scope = config.scope || [];
|
|
61
|
+
if (!config.scope.includes("openid")) {
|
|
62
|
+
config.scope.push("openid");
|
|
63
|
+
}
|
|
64
|
+
const tokens = await ofetch(tokenURL, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
68
|
+
},
|
|
69
|
+
body: new URLSearchParams({
|
|
70
|
+
client_id: config.clientId,
|
|
71
|
+
client_secret: config.clientSecret,
|
|
72
|
+
grant_type: "authorization_code",
|
|
73
|
+
redirect_uri: parsePath(redirectUrl).pathname,
|
|
74
|
+
code
|
|
75
|
+
}).toString()
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
return { error };
|
|
78
|
+
});
|
|
79
|
+
if (tokens.error) {
|
|
80
|
+
const error = createError({
|
|
81
|
+
statusCode: 401,
|
|
82
|
+
message: `Keycloak login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
|
|
83
|
+
data: tokens
|
|
84
|
+
});
|
|
85
|
+
if (!onError)
|
|
86
|
+
throw error;
|
|
87
|
+
return onError(event, error);
|
|
88
|
+
}
|
|
89
|
+
const accessToken = tokens.access_token;
|
|
90
|
+
const user = await ofetch(
|
|
91
|
+
`${realmURL}/protocol/openid-connect/userinfo`,
|
|
92
|
+
{
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${accessToken}`,
|
|
95
|
+
Accept: "application/json"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
if (!user) {
|
|
100
|
+
const error = createError({
|
|
101
|
+
statusCode: 500,
|
|
102
|
+
message: "Could not get Keycloak user",
|
|
103
|
+
data: tokens
|
|
104
|
+
});
|
|
105
|
+
if (!onError)
|
|
106
|
+
throw error;
|
|
107
|
+
return onError(event, error);
|
|
108
|
+
}
|
|
109
|
+
return onSuccess(event, {
|
|
110
|
+
user,
|
|
111
|
+
tokens
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -6,6 +6,7 @@ import { auth0EventHandler } from '../lib/oauth/auth0';
|
|
|
6
6
|
import { microsoftEventHandler } from '../lib/oauth/microsoft';
|
|
7
7
|
import { discordEventHandler } from '../lib/oauth/discord';
|
|
8
8
|
import { battledotnetEventHandler } from '../lib/oauth/battledotnet';
|
|
9
|
+
import { keycloakEventHandler } from '../lib/oauth/keycloak';
|
|
9
10
|
import { linkedinEventHandler } from '../lib/oauth/linkedin';
|
|
10
11
|
export declare const oauth: {
|
|
11
12
|
githubEventHandler: typeof githubEventHandler;
|
|
@@ -16,5 +17,6 @@ export declare const oauth: {
|
|
|
16
17
|
microsoftEventHandler: typeof microsoftEventHandler;
|
|
17
18
|
discordEventHandler: typeof discordEventHandler;
|
|
18
19
|
battledotnetEventHandler: typeof battledotnetEventHandler;
|
|
20
|
+
keycloakEventHandler: typeof keycloakEventHandler;
|
|
19
21
|
linkedinEventHandler: typeof linkedinEventHandler;
|
|
20
22
|
};
|
|
@@ -6,6 +6,7 @@ import { auth0EventHandler } from "../lib/oauth/auth0.mjs";
|
|
|
6
6
|
import { microsoftEventHandler } from "../lib/oauth/microsoft.mjs";
|
|
7
7
|
import { discordEventHandler } from "../lib/oauth/discord.mjs";
|
|
8
8
|
import { battledotnetEventHandler } from "../lib/oauth/battledotnet.mjs";
|
|
9
|
+
import { keycloakEventHandler } from "../lib/oauth/keycloak.mjs";
|
|
9
10
|
import { linkedinEventHandler } from "../lib/oauth/linkedin.mjs";
|
|
10
11
|
export const oauth = {
|
|
11
12
|
githubEventHandler,
|
|
@@ -16,5 +17,6 @@ export const oauth = {
|
|
|
16
17
|
microsoftEventHandler,
|
|
17
18
|
discordEventHandler,
|
|
18
19
|
battledotnetEventHandler,
|
|
20
|
+
keycloakEventHandler,
|
|
19
21
|
linkedinEventHandler
|
|
20
22
|
};
|
|
@@ -6,7 +6,7 @@ export interface SessionHooks {
|
|
|
6
6
|
* - Add extra properties to the session
|
|
7
7
|
* - Throw an error if the session could not be verified (with a database for example)
|
|
8
8
|
*/
|
|
9
|
-
'
|
|
9
|
+
'fetch': (session: UserSession, event: H3Event) => void | Promise<void>;
|
|
10
10
|
/**
|
|
11
11
|
* Called before clearing the session
|
|
12
12
|
*/
|
package/dist/types.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import type { ModuleOptions } from './module'
|
|
2
|
+
import type { ModuleOptions } from './module.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
declare module '@nuxt/schema' {
|
|
@@ -13,4 +13,4 @@ declare module 'nuxt/schema' {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
export type { ModuleOptions, default } from './module'
|
|
16
|
+
export type { ModuleOptions, default } from './module.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Minimalist Auth module for Nuxt with SSR",
|
|
5
5
|
"repository": "Atinux/nuxt-auth-utils",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,27 +30,27 @@
|
|
|
30
30
|
"test:watch": "vitest watch"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nuxt/kit": "^3.
|
|
33
|
+
"@nuxt/kit": "^3.9.0",
|
|
34
34
|
"defu": "^6.1.3",
|
|
35
35
|
"hookable": "^5.5.3",
|
|
36
36
|
"ofetch": "^1.3.3",
|
|
37
37
|
"ohash": "^1.1.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@iconify-json/simple-icons": "^1.1.
|
|
40
|
+
"@iconify-json/simple-icons": "^1.1.86",
|
|
41
41
|
"@nuxt/devtools": "latest",
|
|
42
42
|
"@nuxt/eslint-config": "^0.2.0",
|
|
43
|
-
"@nuxt/module-builder": "^0.5.
|
|
44
|
-
"@nuxt/schema": "^3.
|
|
45
|
-
"@nuxt/test-utils": "^3.
|
|
46
|
-
"@nuxt/ui": "^2.11.
|
|
47
|
-
"@nuxt/ui-pro": "^0.
|
|
48
|
-
"@types/node": "^20.10.
|
|
43
|
+
"@nuxt/module-builder": "^0.5.5",
|
|
44
|
+
"@nuxt/schema": "^3.9.0",
|
|
45
|
+
"@nuxt/test-utils": "^3.9.0",
|
|
46
|
+
"@nuxt/ui": "^2.11.1",
|
|
47
|
+
"@nuxt/ui-pro": "^0.6.1",
|
|
48
|
+
"@types/node": "^20.10.6",
|
|
49
49
|
"changelogen": "^0.5.5",
|
|
50
|
-
"eslint": "^8.
|
|
51
|
-
"nuxt": "^3.
|
|
50
|
+
"eslint": "^8.56.0",
|
|
51
|
+
"nuxt": "^3.9.0",
|
|
52
52
|
"typescript": "^5.3.3",
|
|
53
|
-
"vitest": "^1.
|
|
54
|
-
"vue-tsc": "^1.8.
|
|
53
|
+
"vitest": "^1.1.3",
|
|
54
|
+
"vue-tsc": "^1.8.27"
|
|
55
55
|
}
|
|
56
56
|
}
|