@atlasauth/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 +21 -0
- package/README.md +186 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +47 -0
- package/dist/module.d.ts +47 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +72 -0
- package/dist/runtime/nuxt-shims.d.ts +40 -0
- package/dist/runtime/plugin.d.ts +15 -0
- package/dist/runtime/plugin.js +12 -0
- package/dist/runtime/server/auth.d.ts +48 -0
- package/dist/runtime/server/auth.js +39 -0
- package/dist/runtime/server/auth.test.d.ts +1 -0
- package/dist/runtime/server/auth.test.js +93 -0
- package/dist/runtime/server/backend.d.ts +2 -0
- package/dist/runtime/server/backend.js +18 -0
- package/dist/runtime/server/handshake.d.ts +24 -0
- package/dist/runtime/server/handshake.js +60 -0
- package/dist/runtime/server/index.d.ts +29 -0
- package/dist/runtime/server/index.js +15 -0
- package/dist/runtime/server/middleware.d.ts +12 -0
- package/dist/runtime/server/middleware.js +5 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Atlas
|
|
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,186 @@
|
|
|
1
|
+
# @atlas/nuxt
|
|
2
|
+
|
|
3
|
+
The official **Nuxt 3** module for [Atlas](https://atlasauth.net). A thin Nuxt
|
|
4
|
+
binding: the client half is [`@atlas/vue`](../vue) (the `createAtlas()` plugin,
|
|
5
|
+
its composables and components), the server half mirrors
|
|
6
|
+
[`@atlas/nextjs`](../nextjs) — it verifies the `__session` JWT **locally** with
|
|
7
|
+
`@atlas/backend` inside a Nitro middleware and exposes a `getAtlasAuth(event)`
|
|
8
|
+
helper to your server routes.
|
|
9
|
+
|
|
10
|
+
- **No secrets in the browser.** The client holds only the short-lived session
|
|
11
|
+
JWT in memory. `jwksUrl` / `issuer` live in the **server-only**
|
|
12
|
+
`runtimeConfig` and never reach the client bundle.
|
|
13
|
+
- **No round trip on the hot path.** Server verification is local against cached
|
|
14
|
+
JWKS (§7.3) — the same engine every other Atlas SDK uses.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @atlas/nuxt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configure
|
|
23
|
+
|
|
24
|
+
Add the module and set your instance config. Public values (`publishableKey`,
|
|
25
|
+
`frontendApi`) are safe in the client; the verification values go under the
|
|
26
|
+
server-only `runtimeConfig.atlas`.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// nuxt.config.ts
|
|
30
|
+
export default defineNuxtConfig({
|
|
31
|
+
modules: ['@atlas/nuxt'],
|
|
32
|
+
|
|
33
|
+
atlas: {
|
|
34
|
+
// Public — sent to the browser.
|
|
35
|
+
publishableKey: 'pk_test_...',
|
|
36
|
+
frontendApi: 'https://your-instance.fapi.atlasauth.net',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
runtimeConfig: {
|
|
40
|
+
// Server-only — used by the Nitro side to verify session JWTs.
|
|
41
|
+
atlas: {
|
|
42
|
+
jwksUrl: '', // NUXT_ATLAS_JWKS_URL
|
|
43
|
+
issuer: '', // NUXT_ATLAS_ISSUER
|
|
44
|
+
// authorizedParties: ['https://app.example.com'], // optional azp allowlist
|
|
45
|
+
},
|
|
46
|
+
public: {
|
|
47
|
+
atlas: {
|
|
48
|
+
publishableKey: '', // NUXT_PUBLIC_ATLAS_PUBLISHABLE_KEY
|
|
49
|
+
frontendApi: '', // NUXT_PUBLIC_ATLAS_FRONTEND_API
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In production, prefer environment variables (they override the config above):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
NUXT_PUBLIC_ATLAS_PUBLISHABLE_KEY=pk_live_...
|
|
60
|
+
NUXT_PUBLIC_ATLAS_FRONTEND_API=https://your-instance.fapi.atlasauth.net
|
|
61
|
+
NUXT_ATLAS_JWKS_URL=https://your-instance.fapi.atlasauth.net/.well-known/jwks.json
|
|
62
|
+
NUXT_ATLAS_ISSUER=https://your-instance.fapi.atlasauth.net
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Use in pages and components
|
|
66
|
+
|
|
67
|
+
Every `@atlas/vue` composable and component is **auto-imported** — no `import`
|
|
68
|
+
line needed.
|
|
69
|
+
|
|
70
|
+
**Components** (`SignedIn`, `SignedOut`, `Protect`, `SignIn`, `SignUp`,
|
|
71
|
+
`UserButton`, `OrganizationSwitcher`, `UserProfile`, `OrganizationProfile`,
|
|
72
|
+
`AtlasLoading`, `AtlasLoaded`):
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<template>
|
|
76
|
+
<AtlasLoading><p>Loading…</p></AtlasLoading>
|
|
77
|
+
|
|
78
|
+
<SignedIn>
|
|
79
|
+
<UserButton />
|
|
80
|
+
<Protect permission="org:billing:manage">
|
|
81
|
+
<button>Manage billing</button>
|
|
82
|
+
<template #fallback><p>Ask an admin.</p></template>
|
|
83
|
+
</Protect>
|
|
84
|
+
</SignedIn>
|
|
85
|
+
|
|
86
|
+
<SignedOut>
|
|
87
|
+
<SignIn />
|
|
88
|
+
</SignedOut>
|
|
89
|
+
</template>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Composables** (`useUser`, `useSession`, `useAuth`, `useOrganization`,
|
|
93
|
+
`useSignIn`, `useSignUp`, `useFlow`, `useAtlas`):
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<script setup lang="ts">
|
|
97
|
+
const { isLoaded, isSignedIn, user } = useUser()
|
|
98
|
+
const { userId, getToken, has, signOut } = useAuth()
|
|
99
|
+
|
|
100
|
+
async function callApi() {
|
|
101
|
+
const token = await getToken() // always fresh; refreshes if near expiry
|
|
102
|
+
await $fetch('/api/me', { headers: { Authorization: `Bearer ${token}` } })
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> `<Protect>` and `has()` decide what a user **sees**, never what they may
|
|
108
|
+
> **do** — seeing is not doing. The server checking the same condition is what
|
|
109
|
+
> actually stops them. See the next section.
|
|
110
|
+
|
|
111
|
+
## Read auth in a server route
|
|
112
|
+
|
|
113
|
+
`getAtlasAuth(event)` is auto-imported into every Nitro handler. It returns the
|
|
114
|
+
verified state for the request (memoized — the global middleware already ran the
|
|
115
|
+
verify once).
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// server/api/me.get.ts
|
|
119
|
+
export default defineEventHandler(async (event) => {
|
|
120
|
+
const auth = await getAtlasAuth(event)
|
|
121
|
+
|
|
122
|
+
if (!auth.isSignedIn) {
|
|
123
|
+
return { signedIn: false }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
signedIn: true,
|
|
128
|
+
userId: auth.userId,
|
|
129
|
+
orgId: auth.orgId,
|
|
130
|
+
canManageBilling: auth.has({ permission: 'org:billing:manage' }),
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use `protect()` to hard-stop a route. It throws an HTTP error Nitro maps to a
|
|
136
|
+
status: **401** for an anonymous caller, **403** when signed in but missing the
|
|
137
|
+
role/permission.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// server/api/billing.post.ts
|
|
141
|
+
export default defineEventHandler(async (event) => {
|
|
142
|
+
const auth = await getAtlasAuth(event)
|
|
143
|
+
auth.protect({ permission: 'org:billing:manage' }) // 401 / 403 as appropriate
|
|
144
|
+
|
|
145
|
+
// ...only reached by an authorized caller
|
|
146
|
+
return { ok: true }
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The resolved state is also on `event.context.auth` for any handler or util that
|
|
151
|
+
prefers reading it directly.
|
|
152
|
+
|
|
153
|
+
### The `AtlasAuth` surface
|
|
154
|
+
|
|
155
|
+
| field / method | description |
|
|
156
|
+
| ---------------------------------- | ------------------------------------------------------------------ |
|
|
157
|
+
| `userId` / `sessionId` | the `sub` / `sid` claims, or `null` when signed out |
|
|
158
|
+
| `orgId` / `orgRole` | the active organization and role, or `null` |
|
|
159
|
+
| `isSignedIn` | `true` once a valid token verified |
|
|
160
|
+
| `claims` | the raw verified `SessionClaims`, or `null` |
|
|
161
|
+
| `has(condition?)` | boolean check — `permission` / `role` / `anyPermission` / `allPermissions` |
|
|
162
|
+
| `protect(condition?)` | assert; throws 401 (signed out) / 403 (insufficient) |
|
|
163
|
+
|
|
164
|
+
You can also `import { getAtlasAuth, verifyCarriers, atlasBackend } from '@atlas/nuxt/server'`
|
|
165
|
+
if you prefer explicit imports.
|
|
166
|
+
|
|
167
|
+
## How it fits together
|
|
168
|
+
|
|
169
|
+
| layer | package | what it does |
|
|
170
|
+
| -------------------- | -------------- | ----------------------------------------------------------------------- |
|
|
171
|
+
| client plugin | `@atlas/vue` | `createAtlas()` installed on the Nuxt Vue app (per request during SSR) |
|
|
172
|
+
| composables / comps | `@atlas/vue` | auto-imported into pages/components |
|
|
173
|
+
| Nitro middleware | `@atlas/backend` | verifies `__session` once per request → `event.context.auth` |
|
|
174
|
+
| `getAtlasAuth` | `@atlas/backend` | the server-route auth helper (local verify, cached JWKS) |
|
|
175
|
+
|
|
176
|
+
## Build & test
|
|
177
|
+
|
|
178
|
+
- **Build:** `pnpm build` (via `@nuxt/module-builder` — emits `dist/module.mjs`
|
|
179
|
+
and the runtime under `dist/runtime`).
|
|
180
|
+
- **Typecheck:** `pnpm typecheck`.
|
|
181
|
+
- **Test:** `pnpm test` — a vitest suite (`src/runtime/server/auth.test.ts`)
|
|
182
|
+
covering the Nitro verify helper end-to-end with a real RS256 keypair.
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
package/dist/module.cjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as nuxt_schema from 'nuxt/schema';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `@atlasauth/nuxt` — the official Nuxt 3 module for Atlas.
|
|
5
|
+
*
|
|
6
|
+
* A thin Nuxt binding: the client half is `@atlasauth/vue` (the `createAtlas()`
|
|
7
|
+
* plugin, its composables and components), installed app-wide through a Nuxt
|
|
8
|
+
* plugin and surfaced via auto-imports; the server half mirrors `@atlasauth/nextjs`,
|
|
9
|
+
* verifying the `__session` JWT locally with `@atlasauth/backend` inside a Nitro
|
|
10
|
+
* middleware and exposing a `getAtlasAuth(event)` helper to server routes.
|
|
11
|
+
*
|
|
12
|
+
* The design mirrors the other framework bindings exactly:
|
|
13
|
+
* - the browser holds only the short-lived JWT, never a secret;
|
|
14
|
+
* - the server verifies against cached JWKS with no call back to Atlas on the
|
|
15
|
+
* hot path (§7.3);
|
|
16
|
+
* - `jwksUrl` / `issuer` live in the SERVER-only `runtimeConfig`, never in the
|
|
17
|
+
* public config a client bundle can read.
|
|
18
|
+
*/
|
|
19
|
+
interface ModuleOptions {
|
|
20
|
+
/**
|
|
21
|
+
* The instance publishable key (`pk_...`). Safe to expose to the browser; it
|
|
22
|
+
* ends up in `runtimeConfig.public.atlas`. Prefer the
|
|
23
|
+
* `NUXT_PUBLIC_ATLAS_PUBLISHABLE_KEY` env var in production.
|
|
24
|
+
*/
|
|
25
|
+
publishableKey?: string;
|
|
26
|
+
/** The instance's FAPI origin (e.g. `https://fox.fapi.atlas.dev`). Public. */
|
|
27
|
+
frontendApi?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The instance's JWKS URL, used by the Nitro side to verify session JWTs.
|
|
30
|
+
* SERVER-ONLY — kept in `runtimeConfig.atlas`, never sent to the client.
|
|
31
|
+
*/
|
|
32
|
+
jwksUrl?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Expected token `iss`. SERVER-ONLY. Required for server-side verification —
|
|
35
|
+
* an unchecked issuer would accept a token from any Atlas instance.
|
|
36
|
+
*/
|
|
37
|
+
issuer?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Optional `azp` allowlist. When set, a token minted for another origin is
|
|
40
|
+
* refused server-side (§7.3). SERVER-ONLY.
|
|
41
|
+
*/
|
|
42
|
+
authorizedParties?: string[];
|
|
43
|
+
}
|
|
44
|
+
declare const _default: nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
45
|
+
|
|
46
|
+
export { _default as default };
|
|
47
|
+
export type { ModuleOptions };
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as nuxt_schema from 'nuxt/schema';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `@atlasauth/nuxt` — the official Nuxt 3 module for Atlas.
|
|
5
|
+
*
|
|
6
|
+
* A thin Nuxt binding: the client half is `@atlasauth/vue` (the `createAtlas()`
|
|
7
|
+
* plugin, its composables and components), installed app-wide through a Nuxt
|
|
8
|
+
* plugin and surfaced via auto-imports; the server half mirrors `@atlasauth/nextjs`,
|
|
9
|
+
* verifying the `__session` JWT locally with `@atlasauth/backend` inside a Nitro
|
|
10
|
+
* middleware and exposing a `getAtlasAuth(event)` helper to server routes.
|
|
11
|
+
*
|
|
12
|
+
* The design mirrors the other framework bindings exactly:
|
|
13
|
+
* - the browser holds only the short-lived JWT, never a secret;
|
|
14
|
+
* - the server verifies against cached JWKS with no call back to Atlas on the
|
|
15
|
+
* hot path (§7.3);
|
|
16
|
+
* - `jwksUrl` / `issuer` live in the SERVER-only `runtimeConfig`, never in the
|
|
17
|
+
* public config a client bundle can read.
|
|
18
|
+
*/
|
|
19
|
+
interface ModuleOptions {
|
|
20
|
+
/**
|
|
21
|
+
* The instance publishable key (`pk_...`). Safe to expose to the browser; it
|
|
22
|
+
* ends up in `runtimeConfig.public.atlas`. Prefer the
|
|
23
|
+
* `NUXT_PUBLIC_ATLAS_PUBLISHABLE_KEY` env var in production.
|
|
24
|
+
*/
|
|
25
|
+
publishableKey?: string;
|
|
26
|
+
/** The instance's FAPI origin (e.g. `https://fox.fapi.atlas.dev`). Public. */
|
|
27
|
+
frontendApi?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The instance's JWKS URL, used by the Nitro side to verify session JWTs.
|
|
30
|
+
* SERVER-ONLY — kept in `runtimeConfig.atlas`, never sent to the client.
|
|
31
|
+
*/
|
|
32
|
+
jwksUrl?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Expected token `iss`. SERVER-ONLY. Required for server-side verification —
|
|
35
|
+
* an unchecked issuer would accept a token from any Atlas instance.
|
|
36
|
+
*/
|
|
37
|
+
issuer?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Optional `azp` allowlist. When set, a token minted for another origin is
|
|
40
|
+
* refused server-side (§7.3). SERVER-ONLY.
|
|
41
|
+
*/
|
|
42
|
+
authorizedParties?: string[];
|
|
43
|
+
}
|
|
44
|
+
declare const _default: nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
45
|
+
|
|
46
|
+
export { _default as default };
|
|
47
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addPlugin, addImports, addComponent, addServerHandler, addServerImports } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const module = defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "@atlasauth/nuxt",
|
|
6
|
+
configKey: "atlas",
|
|
7
|
+
compatibility: {
|
|
8
|
+
nuxt: ">=3.0.0"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
defaults: {
|
|
12
|
+
publishableKey: "",
|
|
13
|
+
frontendApi: "",
|
|
14
|
+
jwksUrl: "",
|
|
15
|
+
issuer: "",
|
|
16
|
+
authorizedParties: []
|
|
17
|
+
},
|
|
18
|
+
setup(options, nuxt) {
|
|
19
|
+
const resolver = createResolver(import.meta.url);
|
|
20
|
+
const publicConfig = nuxt.options.runtimeConfig.public.atlas ?? {};
|
|
21
|
+
nuxt.options.runtimeConfig.public.atlas = {
|
|
22
|
+
publishableKey: options.publishableKey || publicConfig.publishableKey || "",
|
|
23
|
+
frontendApi: options.frontendApi || publicConfig.frontendApi || ""
|
|
24
|
+
};
|
|
25
|
+
const privateConfig = nuxt.options.runtimeConfig.atlas ?? {};
|
|
26
|
+
nuxt.options.runtimeConfig.atlas = {
|
|
27
|
+
jwksUrl: options.jwksUrl || privateConfig.jwksUrl || "",
|
|
28
|
+
issuer: options.issuer || privateConfig.issuer || "",
|
|
29
|
+
authorizedParties: options.authorizedParties?.length ? options.authorizedParties : privateConfig.authorizedParties || []
|
|
30
|
+
};
|
|
31
|
+
nuxt.options.build.transpile.push("@atlasauth/vue", "@atlasauth/js", "@atlasauth/authz", resolver.resolve("./runtime"));
|
|
32
|
+
addPlugin({ src: resolver.resolve("./runtime/plugin"), mode: "all" });
|
|
33
|
+
const composables = [
|
|
34
|
+
"useAtlas",
|
|
35
|
+
"useUser",
|
|
36
|
+
"useSession",
|
|
37
|
+
"useAuth",
|
|
38
|
+
"useOrganization",
|
|
39
|
+
"useSignIn",
|
|
40
|
+
"useSignUp",
|
|
41
|
+
"useFlow"
|
|
42
|
+
];
|
|
43
|
+
for (const name of composables) {
|
|
44
|
+
addImports({ name, as: name, from: "@atlasauth/vue" });
|
|
45
|
+
}
|
|
46
|
+
const components = [
|
|
47
|
+
"SignedIn",
|
|
48
|
+
"SignedOut",
|
|
49
|
+
"AtlasLoading",
|
|
50
|
+
"AtlasLoaded",
|
|
51
|
+
"Protect",
|
|
52
|
+
"SignIn",
|
|
53
|
+
"SignUp",
|
|
54
|
+
"UserButton",
|
|
55
|
+
"OrganizationSwitcher",
|
|
56
|
+
"UserProfile",
|
|
57
|
+
"OrganizationProfile"
|
|
58
|
+
];
|
|
59
|
+
for (const name of components) {
|
|
60
|
+
addComponent({ name, export: name, filePath: "@atlasauth/vue" });
|
|
61
|
+
}
|
|
62
|
+
addServerHandler({
|
|
63
|
+
handler: resolver.resolve("./runtime/server/middleware"),
|
|
64
|
+
middleware: true
|
|
65
|
+
});
|
|
66
|
+
addServerImports([
|
|
67
|
+
{ name: "getAtlasAuth", as: "getAtlasAuth", from: resolver.resolve("./runtime/server/index") }
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export { module as default };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback type declarations for the Nuxt virtual modules this package's runtime
|
|
3
|
+
* imports from (`#imports`).
|
|
4
|
+
*
|
|
5
|
+
* When the module is built with `@nuxt/module-builder` (or consumed inside a
|
|
6
|
+
* Nuxt app), Nuxt generates the authoritative types for these virtual modules
|
|
7
|
+
* and they take precedence. This shim only exists so that a standalone
|
|
8
|
+
* `tsc --noEmit` typecheck of `packages/nuxt` — run outside a Nuxt project —
|
|
9
|
+
* still resolves the handful of runtime helpers we use, rather than erroring on
|
|
10
|
+
* an unresolvable `#imports`.
|
|
11
|
+
*
|
|
12
|
+
* The signatures are intentionally narrow: only what the runtime touches.
|
|
13
|
+
*/
|
|
14
|
+
declare module '#imports' {
|
|
15
|
+
/**
|
|
16
|
+
* The subset of Nuxt's runtime app object our plugin touches. Nuxt's own
|
|
17
|
+
* generated types supply the full `NuxtApp` when this package is built inside
|
|
18
|
+
* a Nuxt project; this narrow shape only needs to type the standalone
|
|
19
|
+
* `tsc --noEmit` so the plugin callback's parameter isn't an implicit `any`.
|
|
20
|
+
*/
|
|
21
|
+
export interface NuxtApp {
|
|
22
|
+
vueApp: { use(plugin: unknown): unknown };
|
|
23
|
+
}
|
|
24
|
+
/** Register a Nuxt app plugin (function form — the callback receives the app). */
|
|
25
|
+
export function defineNuxtPlugin<T>(plugin: (nuxtApp: NuxtApp) => T): (nuxtApp: NuxtApp) => T;
|
|
26
|
+
/** Resolve the merged runtime config (public on the client, public + private on the server). */
|
|
27
|
+
export function useRuntimeConfig(): {
|
|
28
|
+
public: {
|
|
29
|
+
atlas: {
|
|
30
|
+
publishableKey: string;
|
|
31
|
+
frontendApi: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
atlas: {
|
|
35
|
+
jwksUrl: string;
|
|
36
|
+
issuer: string;
|
|
37
|
+
authorizedParties: string[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client plugin: installs the `@atlasauth/vue` plugin on Nuxt's Vue app.
|
|
3
|
+
*
|
|
4
|
+
* This is the Nuxt peer of calling `app.use(createAtlas(...))` in a plain Vue
|
|
5
|
+
* app, and of wrapping the tree in `<AtlasProvider>` in React. It runs once per
|
|
6
|
+
* app instance — which on the server means once per request, so each SSR render
|
|
7
|
+
* gets its own reactive client and never shares auth state between two users
|
|
8
|
+
* sharing the process (the exact leak `@atlasauth/vue`'s per-app `inject` avoids).
|
|
9
|
+
*
|
|
10
|
+
* `createAtlas` reads only the PUBLIC config (publishable key + FAPI origin).
|
|
11
|
+
* The verification secrets stay server-side and are never referenced here, so
|
|
12
|
+
* nothing secret is bundled into the client.
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: any;
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createAtlas } from "@atlasauth/vue";
|
|
2
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#imports";
|
|
3
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
4
|
+
const { atlas } = useRuntimeConfig().public;
|
|
5
|
+
nuxtApp.vueApp.use(
|
|
6
|
+
createAtlas({
|
|
7
|
+
publishableKey: atlas.publishableKey,
|
|
8
|
+
// Empty string → let @atlasauth/vue fall back to its default (same-origin FAPI).
|
|
9
|
+
frontendApi: atlas.frontendApi || void 0
|
|
10
|
+
})
|
|
11
|
+
);
|
|
12
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AtlasBackend, type ProtectCondition, type SessionClaims } from '@atlasauth/backend';
|
|
2
|
+
/**
|
|
3
|
+
* The server-side auth surface exposed to Nitro routes — the Nuxt peer of the
|
|
4
|
+
* Next.js `auth()` helper (`@atlasauth/nextjs`'s `createAuthHelper`).
|
|
5
|
+
*
|
|
6
|
+
* A signed-out request is NOT an error: it produces a state with
|
|
7
|
+
* `isSignedIn: false` and every id null, because a route that renders
|
|
8
|
+
* differently for anonymous visitors is the normal case. `protect()` is the
|
|
9
|
+
* opt-in hard stop for routes that must refuse anonymous or under-privileged
|
|
10
|
+
* callers, and it throws a properly-typed HTTP error rather than a bare
|
|
11
|
+
* exception so Nitro maps it to 401/403.
|
|
12
|
+
*/
|
|
13
|
+
export interface AtlasAuth {
|
|
14
|
+
userId: string | null;
|
|
15
|
+
sessionId: string | null;
|
|
16
|
+
orgId: string | null;
|
|
17
|
+
orgRole: string | null;
|
|
18
|
+
isSignedIn: boolean;
|
|
19
|
+
claims: SessionClaims | null;
|
|
20
|
+
/**
|
|
21
|
+
* True when the claims satisfy the condition (an empty condition means
|
|
22
|
+
* "signed in"). Delegates to the shared `@atlasauth/authz` primitive — identical
|
|
23
|
+
* to the client `useAuth().has()` and the React/Next `<Protect>`.
|
|
24
|
+
*/
|
|
25
|
+
has(condition?: ProtectCondition): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Assert access, throwing an HTTP error when unmet: 401 for a signed-out
|
|
28
|
+
* caller, 403 when signed in but missing the role/permission. With no argument
|
|
29
|
+
* it asserts merely "signed in".
|
|
30
|
+
*/
|
|
31
|
+
protect(condition?: ProtectCondition): void;
|
|
32
|
+
}
|
|
33
|
+
/** Build the `AtlasAuth` view over a (possibly null) verified claims set. */
|
|
34
|
+
export declare function build(claims: SessionClaims | null): AtlasAuth;
|
|
35
|
+
/**
|
|
36
|
+
* Verify whatever token a request carries and produce the `AtlasAuth` state.
|
|
37
|
+
*
|
|
38
|
+
* Mirrors `@atlasauth/backend`'s carrier rules: an `Authorization: Bearer` header
|
|
39
|
+
* wins over the `__session` cookie, because a caller that set the header did so
|
|
40
|
+
* deliberately. A token that fails verification yields the signed-out state —
|
|
41
|
+
* never a throw — so a bad or expired token degrades to "not signed in" rather
|
|
42
|
+
* than taking the whole request down (the Nitro peer of `@atlasauth/nextjs`'s
|
|
43
|
+
* `resolveAuth`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function verifyCarriers(backend: AtlasBackend, carriers: {
|
|
46
|
+
authorization?: string | null;
|
|
47
|
+
sessionCookie?: string | null;
|
|
48
|
+
}): Promise<AtlasAuth>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluate,
|
|
3
|
+
hasFromClaims
|
|
4
|
+
} from "@atlasauth/backend";
|
|
5
|
+
import { createError } from "h3";
|
|
6
|
+
function signedOut() {
|
|
7
|
+
return build(null);
|
|
8
|
+
}
|
|
9
|
+
export function build(claims) {
|
|
10
|
+
return {
|
|
11
|
+
userId: claims?.sub ?? null,
|
|
12
|
+
sessionId: claims?.sid ?? null,
|
|
13
|
+
orgId: claims?.org_id ?? null,
|
|
14
|
+
orgRole: claims?.org_role ?? null,
|
|
15
|
+
isSignedIn: claims !== null,
|
|
16
|
+
claims,
|
|
17
|
+
has(condition = {}) {
|
|
18
|
+
return hasFromClaims(claims, condition);
|
|
19
|
+
},
|
|
20
|
+
protect(condition = {}) {
|
|
21
|
+
const outcome = evaluate(claims, condition);
|
|
22
|
+
if (outcome.allowed) return;
|
|
23
|
+
throw createError({
|
|
24
|
+
statusCode: outcome.reason === "signed_out" ? 401 : 403,
|
|
25
|
+
statusMessage: outcome.reason === "signed_out" ? "Unauthorized" : "Forbidden",
|
|
26
|
+
data: { reason: outcome.reason }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function verifyCarriers(backend, carriers) {
|
|
32
|
+
const header = carriers.authorization ?? void 0;
|
|
33
|
+
const bearer = header?.startsWith("Bearer ") ? header.slice(7) : void 0;
|
|
34
|
+
const token = bearer ?? carriers.sessionCookie ?? void 0;
|
|
35
|
+
if (!token) return signedOut();
|
|
36
|
+
const result = await backend.verify(token);
|
|
37
|
+
if (!result.ok) return signedOut();
|
|
38
|
+
return build(result.claims);
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { SignJWT, exportJWK, generateKeyPair } from "jose";
|
|
3
|
+
import { AtlasBackend } from "@atlasauth/backend";
|
|
4
|
+
import { build, verifyCarriers } from "./auth.js";
|
|
5
|
+
const ISSUER = "https://fox.fapi.atlas.test";
|
|
6
|
+
const JWKS_URL = `${ISSUER}/.well-known/jwks.json`;
|
|
7
|
+
async function keyring() {
|
|
8
|
+
const { privateKey, publicKey } = await generateKeyPair("RS256");
|
|
9
|
+
const jwk = { ...await exportJWK(publicKey), kid: "kid-1", alg: "RS256", use: "sig" };
|
|
10
|
+
return { privateKey, jwks: { keys: [jwk] } };
|
|
11
|
+
}
|
|
12
|
+
async function token(privateKey, claims = {}) {
|
|
13
|
+
return new SignJWT({ sid: "sess_1", ...claims }).setProtectedHeader({ alg: "RS256", kid: "kid-1" }).setSubject("user_1").setIssuer(ISSUER).setIssuedAt().setExpirationTime("60s").sign(privateKey);
|
|
14
|
+
}
|
|
15
|
+
const serving = (jwks) => async () => new Response(JSON.stringify(jwks), { status: 200 });
|
|
16
|
+
async function backendFor(jwks) {
|
|
17
|
+
return new AtlasBackend({ jwksUrl: JWKS_URL, issuer: ISSUER, fetchImpl: serving(jwks) });
|
|
18
|
+
}
|
|
19
|
+
describe("verifyCarriers", () => {
|
|
20
|
+
it("resolves a signed-in state from the __session cookie", async () => {
|
|
21
|
+
const { privateKey, jwks } = await keyring();
|
|
22
|
+
const backend = await backendFor(jwks);
|
|
23
|
+
const jwt = await token(privateKey);
|
|
24
|
+
const auth = await verifyCarriers(backend, { sessionCookie: jwt });
|
|
25
|
+
expect(auth.isSignedIn).toBe(true);
|
|
26
|
+
expect(auth.userId).toBe("user_1");
|
|
27
|
+
expect(auth.sessionId).toBe("sess_1");
|
|
28
|
+
expect(auth.has()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it("lets the Authorization header win over the cookie", async () => {
|
|
31
|
+
const { privateKey, jwks } = await keyring();
|
|
32
|
+
const backend = await backendFor(jwks);
|
|
33
|
+
const good = await token(privateKey, { sid: "from_header" });
|
|
34
|
+
const auth = await verifyCarriers(backend, {
|
|
35
|
+
authorization: `Bearer ${good}`,
|
|
36
|
+
sessionCookie: "garbage.cookie.token"
|
|
37
|
+
});
|
|
38
|
+
expect(auth.isSignedIn).toBe(true);
|
|
39
|
+
expect(auth.sessionId).toBe("from_header");
|
|
40
|
+
});
|
|
41
|
+
it("returns signed-out (never throws) for a missing token", async () => {
|
|
42
|
+
const { jwks } = await keyring();
|
|
43
|
+
const backend = await backendFor(jwks);
|
|
44
|
+
const auth = await verifyCarriers(backend, {});
|
|
45
|
+
expect(auth.isSignedIn).toBe(false);
|
|
46
|
+
expect(auth.userId).toBeNull();
|
|
47
|
+
expect(auth.has()).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it("returns signed-out (never throws) for an unverifiable token", async () => {
|
|
50
|
+
const { jwks } = await keyring();
|
|
51
|
+
const backend = await backendFor(jwks);
|
|
52
|
+
const auth = await verifyCarriers(backend, { sessionCookie: "not.a.jwt" });
|
|
53
|
+
expect(auth.isSignedIn).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it("surfaces the active org role and permissions", async () => {
|
|
56
|
+
const { privateKey, jwks } = await keyring();
|
|
57
|
+
const backend = await backendFor(jwks);
|
|
58
|
+
const jwt = await token(privateKey, {
|
|
59
|
+
org_id: "org_1",
|
|
60
|
+
org_role: "admin",
|
|
61
|
+
org_permissions: ["org:billing:manage"]
|
|
62
|
+
});
|
|
63
|
+
const auth = await verifyCarriers(backend, { sessionCookie: jwt });
|
|
64
|
+
expect(auth.orgId).toBe("org_1");
|
|
65
|
+
expect(auth.orgRole).toBe("admin");
|
|
66
|
+
expect(auth.has({ permission: "org:billing:manage" })).toBe(true);
|
|
67
|
+
expect(auth.has({ permission: "org:members:manage" })).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("build().protect", () => {
|
|
71
|
+
it("is a no-op for a signed-in caller", () => {
|
|
72
|
+
const auth = build({ iss: ISSUER, sub: "u", sid: "s", exp: 0 });
|
|
73
|
+
expect(() => auth.protect()).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
it("throws a 401 for an anonymous caller", () => {
|
|
76
|
+
const auth = build(null);
|
|
77
|
+
expect(() => auth.protect()).toThrowError(expect.objectContaining({ statusCode: 401 }));
|
|
78
|
+
});
|
|
79
|
+
it("throws a 403 when signed in but missing the permission", () => {
|
|
80
|
+
const auth = build({
|
|
81
|
+
iss: ISSUER,
|
|
82
|
+
sub: "u",
|
|
83
|
+
sid: "s",
|
|
84
|
+
exp: 0,
|
|
85
|
+
org_id: "org_1",
|
|
86
|
+
org_role: "member",
|
|
87
|
+
org_permissions: []
|
|
88
|
+
});
|
|
89
|
+
expect(() => auth.protect({ permission: "org:billing:manage" })).toThrowError(
|
|
90
|
+
expect.objectContaining({ statusCode: 403 })
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AtlasBackend } from "@atlasauth/backend";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
let cached = null;
|
|
4
|
+
export function atlasBackend() {
|
|
5
|
+
if (cached) return cached;
|
|
6
|
+
const { atlas } = useRuntimeConfig();
|
|
7
|
+
if (!atlas.jwksUrl || !atlas.issuer) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"@atlasauth/nuxt: server-side auth needs `runtimeConfig.atlas.jwksUrl` and `runtimeConfig.atlas.issuer` (set them via the module options or the NUXT_ATLAS_JWKS_URL / NUXT_ATLAS_ISSUER env vars)."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
cached = new AtlasBackend({
|
|
13
|
+
jwksUrl: atlas.jwksUrl,
|
|
14
|
+
issuer: atlas.issuer,
|
|
15
|
+
authorizedParties: atlas.authorizedParties?.length ? atlas.authorizedParties : void 0
|
|
16
|
+
});
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type H3Event } from 'h3';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-property SSO handshake for a Nuxt/Nitro route — the peer of the Next.js
|
|
4
|
+
* `atlasMiddleware({ handshake: true })`. Call it at the top of a protected
|
|
5
|
+
* server route: if the visitor has no local session it bounces them once
|
|
6
|
+
* through the Atlas handshake, and on the way back it redeems the cross-domain
|
|
7
|
+
* nonce for a fresh FIRST-PARTY session on this property (setting `__session` +
|
|
8
|
+
* `__atlas_rt`). Returns `true` when it has issued a redirect (the caller should
|
|
9
|
+
* stop and return); `false` when the request may proceed (signed in, or the
|
|
10
|
+
* handshake reported signed-out — send them to your sign-in then).
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* export default defineEventHandler(async (event) => {
|
|
14
|
+
* if (await atlasHandshake(event)) return; // redirected
|
|
15
|
+
* const auth = await getAtlasAuth(event);
|
|
16
|
+
* auth.protect();
|
|
17
|
+
* return { userId: auth.userId };
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Requires `runtimeConfig.atlas.publishableKey` (and `jwksUrl`, from which the
|
|
22
|
+
* FAPI origin is derived unless `atlas.fapiOrigin` is set).
|
|
23
|
+
*/
|
|
24
|
+
export declare function atlasHandshake(event: H3Event): Promise<boolean>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getCookie, getQuery, getRequestURL, sendRedirect, setCookie } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createPkcePair, redeemHandshake } from "@atlasauth/backend";
|
|
4
|
+
import { getAtlasAuth } from "./index.js";
|
|
5
|
+
export async function atlasHandshake(event) {
|
|
6
|
+
const { atlas } = useRuntimeConfig();
|
|
7
|
+
const publishableKey = atlas.publishableKey;
|
|
8
|
+
const fapiOrigin = atlas.fapiOrigin ?? (atlas.jwksUrl ? new URL(atlas.jwksUrl).origin : void 0);
|
|
9
|
+
if (!publishableKey || !fapiOrigin) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const q = getQuery(event);
|
|
13
|
+
const url = getRequestURL(event);
|
|
14
|
+
if (q.__atlas_hs === "ok" && q.__atlas_hu && q.__atlas_hn) {
|
|
15
|
+
const codeVerifier = getCookie(event, "__atlas_hv");
|
|
16
|
+
const session = codeVerifier ? await redeemHandshake({
|
|
17
|
+
fapiOrigin,
|
|
18
|
+
publishableKey,
|
|
19
|
+
userId: q.__atlas_hu,
|
|
20
|
+
nonce: q.__atlas_hn,
|
|
21
|
+
codeVerifier
|
|
22
|
+
}) : null;
|
|
23
|
+
const clean = new URL(url.toString());
|
|
24
|
+
for (const k of ["__atlas_hs", "__atlas_hu", "__atlas_hn"]) clean.searchParams.delete(k);
|
|
25
|
+
setCookie(event, "__atlas_hv", "", { path: "/", maxAge: 0 });
|
|
26
|
+
if (session) {
|
|
27
|
+
setCookie(event, "__session", session.jwt, { path: "/", sameSite: "lax", secure: true });
|
|
28
|
+
setCookie(event, "__atlas_rt", session.refreshToken, {
|
|
29
|
+
path: "/",
|
|
30
|
+
httpOnly: true,
|
|
31
|
+
sameSite: "lax",
|
|
32
|
+
secure: true
|
|
33
|
+
});
|
|
34
|
+
await sendRedirect(event, clean.pathname + clean.search, 303);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
clean.searchParams.set("__atlas_hs", "failed");
|
|
38
|
+
await sendRedirect(event, clean.pathname + clean.search, 303);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const auth = await getAtlasAuth(event);
|
|
42
|
+
if (auth.userId) return false;
|
|
43
|
+
if (!q.__atlas_hs) {
|
|
44
|
+
const { verifier, challenge } = await createPkcePair();
|
|
45
|
+
setCookie(event, "__atlas_hv", verifier, {
|
|
46
|
+
path: "/",
|
|
47
|
+
httpOnly: true,
|
|
48
|
+
sameSite: "lax",
|
|
49
|
+
secure: true,
|
|
50
|
+
maxAge: 300
|
|
51
|
+
});
|
|
52
|
+
const hs = new URL("/v1/client/handshake", fapiOrigin);
|
|
53
|
+
hs.searchParams.set("publishable_key", publishableKey);
|
|
54
|
+
hs.searchParams.set("redirect_url", url.toString());
|
|
55
|
+
hs.searchParams.set("code_challenge", challenge);
|
|
56
|
+
await sendRedirect(event, hs.toString(), 303);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type H3Event } from 'h3';
|
|
2
|
+
import { type AtlasAuth } from './auth.js';
|
|
3
|
+
export type { AtlasAuth } from './auth.js';
|
|
4
|
+
export { verifyCarriers, build } from './auth.js';
|
|
5
|
+
export { atlasBackend } from './backend.js';
|
|
6
|
+
export { atlasHandshake } from './handshake.js';
|
|
7
|
+
/**
|
|
8
|
+
* Read the verified auth state for the current request — the server-route peer
|
|
9
|
+
* of the client `useAuth()`.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* export default defineEventHandler(async (event) => {
|
|
13
|
+
* const auth = await getAtlasAuth(event)
|
|
14
|
+
* auth.protect() // 401 if not signed in
|
|
15
|
+
* return { userId: auth.userId }
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The result is memoized on `event.context.auth`, so the global middleware and
|
|
20
|
+
* any number of handlers on the same request share ONE verification. The header
|
|
21
|
+
* takes precedence over the `__session` cookie, matching `@atlasauth/backend`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAtlasAuth(event: H3Event): Promise<AtlasAuth>;
|
|
24
|
+
declare module 'h3' {
|
|
25
|
+
interface H3EventContext {
|
|
26
|
+
/** The Atlas auth state resolved for this request by `@atlasauth/nuxt`. */
|
|
27
|
+
auth?: AtlasAuth;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getRequestHeader, getCookie } from "h3";
|
|
2
|
+
import { atlasBackend } from "./backend.js";
|
|
3
|
+
import { verifyCarriers } from "./auth.js";
|
|
4
|
+
export { verifyCarriers, build } from "./auth.js";
|
|
5
|
+
export { atlasBackend } from "./backend.js";
|
|
6
|
+
export { atlasHandshake } from "./handshake.js";
|
|
7
|
+
export async function getAtlasAuth(event) {
|
|
8
|
+
if (event.context.auth) return event.context.auth;
|
|
9
|
+
const auth = await verifyCarriers(atlasBackend(), {
|
|
10
|
+
authorization: getRequestHeader(event, "authorization") ?? null,
|
|
11
|
+
sessionCookie: getCookie(event, "__session") ?? null
|
|
12
|
+
});
|
|
13
|
+
event.context.auth = auth;
|
|
14
|
+
return auth;
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Nitro middleware — the Nuxt peer of the Next.js `atlasMiddleware`.
|
|
3
|
+
*
|
|
4
|
+
* It verifies the `__session` JWT once, up front, and stashes the result on
|
|
5
|
+
* `event.context.auth`, so every downstream `defineEventHandler` (and any server
|
|
6
|
+
* util) reads the same resolved auth without repeating the verify. It does NOT
|
|
7
|
+
* enforce a route policy here — verification is local and cheap, but redirect /
|
|
8
|
+
* 401 decisions belong to each route via `auth.protect(...)`, keeping the
|
|
9
|
+
* middleware a pure "who is this" step rather than a "may they" one.
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
12
|
+
export default _default;
|
package/dist/types.d.mts
ADDED
package/dist/types.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atlasauth/nuxt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Official Nuxt 3 module for Atlas — SSR session verification (Nitro) built on the @atlasauth/vue client.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/module.mjs",
|
|
8
|
+
"types": "./dist/types.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types.d.mts",
|
|
12
|
+
"import": "./dist/module.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/runtime/server/index.d.ts",
|
|
16
|
+
"import": "./dist/runtime/server/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@nuxt/kit": "^3.13.0",
|
|
26
|
+
"@atlasauth/js": "0.1.0",
|
|
27
|
+
"@atlasauth/backend": "0.1.0",
|
|
28
|
+
"@atlasauth/authz": "0.1.0",
|
|
29
|
+
"@atlasauth/vue": "0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"nuxt": "^3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@nuxt/module-builder": "^0.8.4",
|
|
36
|
+
"@nuxt/schema": "^3.13.0",
|
|
37
|
+
"h3": "^1.12.0",
|
|
38
|
+
"jose": "^5.9.6",
|
|
39
|
+
"nuxt": "^3.13.0",
|
|
40
|
+
"typescript": "^5.6.3",
|
|
41
|
+
"vitest": "^2.1.4",
|
|
42
|
+
"vue": "^3.4.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "nuxt-module-build build",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
|
+
"test": "vitest run"
|
|
51
|
+
}
|
|
52
|
+
}
|