@cboxdk/id-nuxt 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Cbox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @cboxdk/id-nuxt
2
+
3
+ Nuxt module for [Cbox ID](https://github.com/cboxdk/laravel-id). It wires the
4
+ [`@cboxdk/id-js`](https://github.com/cboxdk/id-js) OIDC client to your Nuxt app:
5
+ drop-in **sign-in / callback / sign-out** routes, a sealed session, and a
6
+ `useCboxUser()` composable — add authentication with one module entry.
7
+
8
+ Pair it with [`@cboxdk/id-vue`](https://github.com/cboxdk/id-vue) for the
9
+ `<CboxUserButton>` and other widgets.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @cboxdk/id-nuxt
15
+ ```
16
+
17
+ ## Configure
18
+
19
+ ```ts
20
+ // nuxt.config.ts
21
+ export default defineNuxtConfig({
22
+ modules: ['@cboxdk/id-nuxt'],
23
+ cboxId: {
24
+ // or set CBOX_ID_ISSUER / CBOX_ID_CLIENT_ID / CBOX_ID_CLIENT_SECRET / CBOX_ID_REDIRECT_URI
25
+ issuer: 'https://id.acme.com',
26
+ clientId: process.env.CBOX_ID_CLIENT_ID,
27
+ clientSecret: process.env.CBOX_ID_CLIENT_SECRET,
28
+ redirectUri: 'https://app.acme.com/auth/callback',
29
+ },
30
+ });
31
+ ```
32
+
33
+ Set a session secret so the session cookie is sealed:
34
+
35
+ ```dotenv
36
+ CBOX_ID_SESSION_PASSWORD=at-least-32-characters-of-random
37
+ ```
38
+
39
+ ## Use
40
+
41
+ The module registers these routes for you:
42
+
43
+ | Route | Does |
44
+ |---|---|
45
+ | `GET /auth/sign-in` | starts login (accepts `?redirect=/where/next`) |
46
+ | `GET /auth/callback` | verifies the login and stores the session |
47
+ | `GET /auth/sign-out` | clears the session and logs out |
48
+ | `GET /api/_cbox/user` | the current user as JSON (used internally) |
49
+
50
+ Link to them and read the user reactively anywhere:
51
+
52
+ ```vue
53
+ <script setup lang="ts">
54
+ const user = useCboxUser();
55
+ </script>
56
+
57
+ <template>
58
+ <div v-if="user">
59
+ Hi {{ user.name }} — <a href="/auth/sign-out">Sign out</a>
60
+ </div>
61
+ <a v-else href="/auth/sign-in">Sign in</a>
62
+ </template>
63
+ ```
64
+
65
+ Protect a route with middleware:
66
+
67
+ ```ts
68
+ // middleware/auth.ts
69
+ export default defineNuxtRouteMiddleware(() => {
70
+ const user = useCboxUser();
71
+ if (!user.value) {
72
+ return navigateTo('/auth/sign-in?redirect=' + encodeURIComponent(useRoute().fullPath));
73
+ }
74
+ });
75
+ ```
76
+
77
+ ## Options
78
+
79
+ | Option | Default | Notes |
80
+ |---|---|---|
81
+ | `issuer` / `clientId` / `clientSecret` / `redirectUri` | from env | the Cbox ID connection |
82
+ | `scopes` | `openid profile email` | requested at login |
83
+ | `accountPath` | `/settings` | hosted profile page path |
84
+ | `loginPath` / `callbackPath` / `logoutPath` | `/auth/*` | override the route paths |
85
+
86
+ ## Scope
87
+
88
+ This module handles login, session and sign-out against a Cbox ID instance. Profile
89
+ management (password, MFA, passkeys) is hosted by the instance — link users to
90
+ `accountPath` there. SSO/SCIM/org administration are platform capabilities of
91
+ [`cboxdk/laravel-id`](https://github.com/cboxdk/laravel-id), not this module.
92
+
93
+ ## License
94
+
95
+ MIT © Cbox.
@@ -0,0 +1,31 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ /** Base URL of the Cbox ID instance (or set CBOX_ID_ISSUER). */
5
+ issuer?: string;
6
+ /** Your OAuth client id (or set CBOX_ID_CLIENT_ID). */
7
+ clientId?: string;
8
+ /** Your client secret (or set CBOX_ID_CLIENT_SECRET). */
9
+ clientSecret?: string;
10
+ /** Your callback URL, registered on the client (or set CBOX_ID_REDIRECT_URI). */
11
+ redirectUri?: string;
12
+ /** The instance's hosted account path. Defaults to /settings. */
13
+ accountPath?: string;
14
+ /** Scopes requested at login. */
15
+ scopes?: string[];
16
+ /** Route that starts login. Defaults to /auth/sign-in. */
17
+ loginPath?: string;
18
+ /** Route that handles the callback. Defaults to /auth/callback. */
19
+ callbackPath?: string;
20
+ /** Route that signs out. Defaults to /auth/sign-out. */
21
+ logoutPath?: string;
22
+ }
23
+ /**
24
+ * Nuxt module for Cbox ID. Registers the sign-in / callback / sign-out routes and a
25
+ * `useCboxUser()` composable, all backed by @cboxdk/id-js. Configure it under the
26
+ * `cboxId` key or via environment variables.
27
+ */
28
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
29
+
30
+ export { _default as default };
31
+ export type { ModuleOptions };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@cboxdk/id-nuxt",
3
+ "configKey": "cboxId",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "0.1.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }
@@ -0,0 +1,64 @@
1
+ import { defineNuxtModule, createResolver, addServerHandler, addImportsDir, addPlugin } from '@nuxt/kit';
2
+ import { defu } from 'defu';
3
+
4
+ const module$1 = defineNuxtModule({
5
+ meta: {
6
+ name: "@cboxdk/id-nuxt",
7
+ configKey: "cboxId",
8
+ compatibility: { nuxt: ">=3.0.0" }
9
+ },
10
+ defaults: {
11
+ accountPath: "/settings",
12
+ loginPath: "/auth/sign-in",
13
+ callbackPath: "/auth/callback",
14
+ logoutPath: "/auth/sign-out"
15
+ },
16
+ setup(options, nuxt) {
17
+ const resolver = createResolver(import.meta.url);
18
+ nuxt.options.runtimeConfig.cboxId = defu(
19
+ nuxt.options.runtimeConfig.cboxId,
20
+ {
21
+ issuer: options.issuer ?? process.env.CBOX_ID_ISSUER ?? "",
22
+ clientId: options.clientId ?? process.env.CBOX_ID_CLIENT_ID ?? "",
23
+ clientSecret: options.clientSecret ?? process.env.CBOX_ID_CLIENT_SECRET ?? "",
24
+ redirectUri: options.redirectUri ?? process.env.CBOX_ID_REDIRECT_URI ?? "",
25
+ accountPath: options.accountPath,
26
+ scopes: options.scopes,
27
+ sessionPassword: process.env.CBOX_ID_SESSION_PASSWORD ?? ""
28
+ }
29
+ );
30
+ nuxt.options.runtimeConfig.public.cboxId = defu(
31
+ nuxt.options.runtimeConfig.public.cboxId,
32
+ {
33
+ loginPath: options.loginPath,
34
+ logoutPath: options.logoutPath,
35
+ accountPath: options.accountPath
36
+ }
37
+ );
38
+ nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
39
+ addServerHandler({
40
+ route: options.loginPath,
41
+ method: "get",
42
+ handler: resolver.resolve("./runtime/server/routes/sign-in.get")
43
+ });
44
+ addServerHandler({
45
+ route: options.callbackPath,
46
+ method: "get",
47
+ handler: resolver.resolve("./runtime/server/routes/callback.get")
48
+ });
49
+ addServerHandler({
50
+ route: options.logoutPath,
51
+ method: "get",
52
+ handler: resolver.resolve("./runtime/server/routes/sign-out.get")
53
+ });
54
+ addServerHandler({
55
+ route: "/api/_cbox/user",
56
+ method: "get",
57
+ handler: resolver.resolve("./runtime/server/routes/user.get")
58
+ });
59
+ addImportsDir(resolver.resolve("./runtime/composables"));
60
+ addPlugin(resolver.resolve("./runtime/plugin"));
61
+ }
62
+ });
63
+
64
+ export { module$1 as default };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The signed-in Cbox ID user (reactive), or null when signed out. Hydrated by the
3
+ * module's plugin from the server session.
4
+ */
5
+ export declare function useCboxUser(): any;
@@ -0,0 +1,4 @@
1
+ import { useState } from "#imports";
2
+ export function useCboxUser() {
3
+ return useState("cbox-id-user", () => null);
4
+ }
@@ -0,0 +1,3 @@
1
+ /** Hydrate the current user from the server session once, on app start. */
2
+ declare const _default: any;
3
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import { defineNuxtPlugin, useRequestFetch } from "#imports";
2
+ import { useCboxUser } from "./composables/useCboxUser.js";
3
+ export default defineNuxtPlugin(async () => {
4
+ const user = useCboxUser();
5
+ if (user.value !== null) {
6
+ return;
7
+ }
8
+ try {
9
+ user.value = await useRequestFetch()("/api/_cbox/user");
10
+ } catch {
11
+ user.value = null;
12
+ }
13
+ });
@@ -0,0 +1,3 @@
1
+ /** Complete login: verify state, exchange the code, verify the id_token, store user. */
2
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
3
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { defineEventHandler, getQuery, sendRedirect, useSession } from "h3";
2
+ import { cboxSessionConfig, getCboxClient } from "../utils/client.js";
3
+ function asString(value) {
4
+ return typeof value === "string" ? value : void 0;
5
+ }
6
+ export default defineEventHandler(async (event) => {
7
+ const client = getCboxClient(event);
8
+ const session = await useSession(event, cboxSessionConfig(event));
9
+ const query = getQuery(event);
10
+ const user = await client.authenticate({
11
+ params: {
12
+ code: asString(query.code),
13
+ state: asString(query.state),
14
+ error: asString(query.error),
15
+ error_description: asString(query.error_description)
16
+ },
17
+ stored: {
18
+ state: session.data.state ?? "",
19
+ codeVerifier: session.data.codeVerifier ?? "",
20
+ nonce: session.data.nonce ?? ""
21
+ }
22
+ });
23
+ const redirectTo = session.data.redirectTo ?? "/";
24
+ await session.update({
25
+ user: {
26
+ id: user.id,
27
+ email: user.email,
28
+ name: user.name,
29
+ organizationId: user.organizationId
30
+ },
31
+ state: void 0,
32
+ codeVerifier: void 0,
33
+ nonce: void 0,
34
+ redirectTo: void 0
35
+ });
36
+ return sendRedirect(event, redirectTo);
37
+ });
@@ -0,0 +1,3 @@
1
+ /** Start login: create the authorization request, stash PKCE/state/nonce, redirect. */
2
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
3
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { defineEventHandler, getQuery, sendRedirect, useSession } from "h3";
2
+ import { cboxSessionConfig, getCboxClient } from "../utils/client.js";
3
+ export default defineEventHandler(async (event) => {
4
+ const client = getCboxClient(event);
5
+ const request = await client.createAuthorizationRequest();
6
+ const session = await useSession(event, cboxSessionConfig(event));
7
+ const redirect = getQuery(event).redirect;
8
+ await session.update({
9
+ state: request.state,
10
+ codeVerifier: request.codeVerifier,
11
+ nonce: request.nonce,
12
+ redirectTo: typeof redirect === "string" ? redirect : "/"
13
+ });
14
+ return sendRedirect(event, request.url);
15
+ });
@@ -0,0 +1,3 @@
1
+ /** Clear the session and redirect to RP-initiated logout (or home). */
2
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
3
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { defineEventHandler, getRequestURL, sendRedirect, useSession } from "h3";
2
+ import { cboxSessionConfig, getCboxClient } from "../utils/client.js";
3
+ export default defineEventHandler(async (event) => {
4
+ const client = getCboxClient(event);
5
+ const session = await useSession(event, cboxSessionConfig(event));
6
+ await session.clear();
7
+ const origin = getRequestURL(event).origin;
8
+ const logoutUrl = await client.logoutUrl(origin);
9
+ return sendRedirect(event, logoutUrl ?? "/");
10
+ });
@@ -0,0 +1,4 @@
1
+ import type { CboxSessionUser } from '../../types.js';
2
+ /** Return the signed-in user from the session, or null. */
3
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<CboxSessionUser | null>>;
4
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import { defineEventHandler, useSession } from "h3";
2
+ import { cboxSessionConfig } from "../utils/client.js";
3
+ export default defineEventHandler(async (event) => {
4
+ const session = await useSession(event, cboxSessionConfig(event));
5
+ return session.data.user ?? null;
6
+ });
@@ -0,0 +1,6 @@
1
+ import { CboxIdClient } from '@cboxdk/id-js';
2
+ import type { H3Event, SessionConfig } from 'h3';
3
+ /** Build a CboxIdClient from the module's server runtime config. */
4
+ export declare function getCboxClient(event: H3Event): CboxIdClient;
5
+ /** The sealed-cookie session config (h3 useSession). */
6
+ export declare function cboxSessionConfig(event: H3Event): SessionConfig;
@@ -0,0 +1,24 @@
1
+ import { CboxIdClient } from "@cboxdk/id-js";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export function getCboxClient(event) {
4
+ const cfg = useRuntimeConfig(event).cboxId;
5
+ const config = {
6
+ issuer: cfg.issuer,
7
+ clientId: cfg.clientId,
8
+ redirectUri: cfg.redirectUri
9
+ };
10
+ if (cfg.clientSecret) {
11
+ config.clientSecret = cfg.clientSecret;
12
+ }
13
+ if (cfg.accountPath) {
14
+ config.accountPath = cfg.accountPath;
15
+ }
16
+ if (cfg.scopes && cfg.scopes.length > 0) {
17
+ config.scopes = cfg.scopes;
18
+ }
19
+ return new CboxIdClient(config);
20
+ }
21
+ export function cboxSessionConfig(event) {
22
+ const cfg = useRuntimeConfig(event).cboxId;
23
+ return { password: cfg.sessionPassword, name: "cbox_id" };
24
+ }
@@ -0,0 +1,25 @@
1
+ /** The signed-in user we keep in the session and expose via useCboxUser(). */
2
+ export interface CboxSessionUser {
3
+ id: string;
4
+ email: string | null;
5
+ name: string | null;
6
+ organizationId: string | null;
7
+ }
8
+ /** The shape of our sealed session. */
9
+ export interface CboxSessionData {
10
+ state?: string;
11
+ codeVerifier?: string;
12
+ nonce?: string;
13
+ redirectTo?: string;
14
+ user?: CboxSessionUser;
15
+ }
16
+ /** The private runtime config this module reads server-side. */
17
+ export interface CboxRuntimeConfig {
18
+ issuer: string;
19
+ clientId: string;
20
+ clientSecret: string;
21
+ redirectUri: string;
22
+ accountPath?: string;
23
+ scopes?: string[];
24
+ sessionPassword: string;
25
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@cboxdk/id-nuxt",
3
+ "version": "0.1.0",
4
+ "description": "Nuxt module for Cbox ID — drop-in sign-in / callback / sign-out routes and session handling built on @cboxdk/id-js. Add authentication to a Nuxt app with one module entry.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Sylvester Damgaard <sn@cbox.dk>",
8
+ "homepage": "https://github.com/cboxdk/id-nuxt",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/cboxdk/id-nuxt.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/cboxdk/id-nuxt/issues"
15
+ },
16
+ "keywords": [
17
+ "cbox-id",
18
+ "nuxt",
19
+ "nuxt-module",
20
+ "authentication",
21
+ "oidc",
22
+ "sso"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/types.d.mts",
27
+ "import": "./dist/module.mjs"
28
+ }
29
+ },
30
+ "main": "./dist/module.mjs",
31
+ "typesVersions": {
32
+ "*": {
33
+ ".": [
34
+ "./dist/types.d.mts"
35
+ ]
36
+ }
37
+ },
38
+ "types": "./dist/types.d.mts",
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "scripts": {
43
+ "prepare-types": "nuxt-module-build prepare",
44
+ "build": "nuxt-module-build build",
45
+ "check": "npm run prepare-types && npm run build"
46
+ },
47
+ "dependencies": {
48
+ "@cboxdk/id-js": "^0.1.0",
49
+ "@nuxt/kit": "^3.0.0 || ^4.0.0",
50
+ "defu": "^6.1.0",
51
+ "h3": "^1.13.0"
52
+ },
53
+ "devDependencies": {
54
+ "@nuxt/module-builder": "^1.0.0",
55
+ "@nuxt/schema": "^3.0.0 || ^4.0.0",
56
+ "typescript": "^5.9.2"
57
+ }
58
+ }