@baseworks/auth 0.2.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/dist/chunk-3NJASEF4.js +69 -0
- package/dist/chunk-6TUNNS2B.js +25 -0
- package/dist/chunk-BL74TFCV.js +23 -0
- package/dist/chunk-BMPRMOI7.js +23 -0
- package/dist/chunk-C4V5LCFA.js +47 -0
- package/dist/chunk-M7EACPIB.js +33 -0
- package/dist/chunk-VBIQJKUU.js +20 -0
- package/dist/chunk-VUB4GTMI.js +402 -0
- package/dist/cli-auth.d.ts +34 -0
- package/dist/cli-auth.js +8 -0
- package/dist/edge.d.ts +17 -0
- package/dist/edge.js +155 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +54 -0
- package/dist/oidc-human.d.ts +41 -0
- package/dist/oidc-human.js +7 -0
- package/dist/oidc.d.ts +29 -0
- package/dist/oidc.js +6 -0
- package/dist/pkce.d.ts +35 -0
- package/dist/pkce.js +8 -0
- package/dist/session.d.ts +42 -0
- package/dist/session.js +25 -0
- package/dist/token.d.ts +10 -0
- package/dist/token.js +10 -0
- package/dist/url-helpers.d.ts +11 -0
- package/dist/url-helpers.js +22 -0
- package/dist/zitadel.d.ts +28 -0
- package/dist/zitadel.js +6 -0
- package/package.json +43 -0
- package/src/cli-auth.ts +103 -0
- package/src/edge.ts +189 -0
- package/src/index.ts +39 -0
- package/src/oidc-human.ts +60 -0
- package/src/oidc.ts +57 -0
- package/src/pkce.ts +73 -0
- package/src/session.ts +538 -0
- package/src/token.ts +21 -0
- package/src/url-helpers.ts +66 -0
- package/src/zitadel.ts +56 -0
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OidcSession } from './session.js';
|
|
2
|
+
import 'next/server';
|
|
3
|
+
|
|
4
|
+
type EdgeAuthProvider = "anonymous" | "cookie" | "pomerium";
|
|
5
|
+
type EdgeSession = OidcSession & {
|
|
6
|
+
groups?: string[];
|
|
7
|
+
provider: EdgeAuthProvider;
|
|
8
|
+
rawClaims?: Record<string, unknown>;
|
|
9
|
+
role?: string;
|
|
10
|
+
username?: string;
|
|
11
|
+
};
|
|
12
|
+
declare function getEdgeSession(): Promise<EdgeSession>;
|
|
13
|
+
declare function getPomeriumAuthenticateUrl(redirectTo: string, origin?: string): string | null;
|
|
14
|
+
declare function handleEdgeLogin(request: Request): void;
|
|
15
|
+
declare function getPomeriumSignOutUrl(redirectTo: string, origin?: string): string | null;
|
|
16
|
+
|
|
17
|
+
export { type EdgeAuthProvider, type EdgeSession, getEdgeSession, getPomeriumAuthenticateUrl, getPomeriumSignOutUrl, handleEdgeLogin };
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSessionFromCookies
|
|
3
|
+
} from "./chunk-VUB4GTMI.js";
|
|
4
|
+
import {
|
|
5
|
+
buildPublicUrl,
|
|
6
|
+
normalizeUrlLike
|
|
7
|
+
} from "./chunk-3NJASEF4.js";
|
|
8
|
+
|
|
9
|
+
// src/edge.ts
|
|
10
|
+
import { headers } from "next/headers";
|
|
11
|
+
import { redirect } from "next/navigation";
|
|
12
|
+
function firstHeader(headerStore, names) {
|
|
13
|
+
for (const name of names) {
|
|
14
|
+
const value = headerStore.get(name);
|
|
15
|
+
if (value) return value;
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
function decodeBase64Url(value) {
|
|
20
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
21
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
22
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
23
|
+
}
|
|
24
|
+
function parseJwtPayload(token) {
|
|
25
|
+
try {
|
|
26
|
+
const [, payload] = token.split(".");
|
|
27
|
+
if (!payload) return null;
|
|
28
|
+
return JSON.parse(decodeBase64Url(payload));
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function stringClaim(claims, keys) {
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const value = claims[key];
|
|
36
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
const first = value.find((item) => typeof item === "string" && item.trim());
|
|
39
|
+
if (first) return first;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function parseHeaderClaim(value) {
|
|
45
|
+
if (!value) return void 0;
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(value);
|
|
48
|
+
if (typeof parsed === "string" && parsed.trim()) return parsed;
|
|
49
|
+
if (Array.isArray(parsed)) {
|
|
50
|
+
const first = parsed.find((item) => typeof item === "string" && item.trim());
|
|
51
|
+
if (first) return first;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
return value.trim() || void 0;
|
|
56
|
+
}
|
|
57
|
+
function stringHeaderClaim(headerStore, names) {
|
|
58
|
+
for (const name of names) {
|
|
59
|
+
const value = parseHeaderClaim(headerStore.get(name) ?? void 0);
|
|
60
|
+
if (value) return value;
|
|
61
|
+
}
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
function stringArrayClaim(claims, keys) {
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
const value = claims[key];
|
|
67
|
+
if (typeof value === "string" && value.trim()) return [value];
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
const strings = value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
70
|
+
if (strings.length) return strings;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
function stringArrayHeaderClaim(headerStore, names) {
|
|
76
|
+
for (const name of names) {
|
|
77
|
+
const raw = headerStore.get(name);
|
|
78
|
+
if (!raw) continue;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (typeof parsed === "string" && parsed.trim()) return [parsed];
|
|
82
|
+
if (Array.isArray(parsed)) {
|
|
83
|
+
const strings = parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
84
|
+
if (strings.length) return strings;
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
88
|
+
if (values.length) return values;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
function parsePomeriumSession(headerStore) {
|
|
94
|
+
const assertion = firstHeader(headerStore, ["x-pomerium-jwt-assertion", "x-pomerium-jwt"]);
|
|
95
|
+
const claims = assertion ? parseJwtPayload(assertion) : {};
|
|
96
|
+
const email = stringHeaderClaim(headerStore, ["x-pomerium-claim-email", "x-forwarded-email"]) ?? stringClaim(claims ?? {}, ["email"]);
|
|
97
|
+
const subject = stringHeaderClaim(headerStore, ["x-pomerium-claim-sub", "x-pomerium-claim-user", "x-user-id"]) ?? stringClaim(claims ?? {}, ["sub", "user", "id"]);
|
|
98
|
+
const name = stringHeaderClaim(headerStore, ["x-pomerium-claim-name", "x-forwarded-user"]) ?? stringClaim(claims ?? {}, ["name", "preferred_username"]);
|
|
99
|
+
const pictureUrl = stringHeaderClaim(headerStore, ["x-pomerium-claim-picture"]) ?? stringClaim(claims ?? {}, ["picture"]);
|
|
100
|
+
const username = stringHeaderClaim(headerStore, ["x-pomerium-claim-username", "x-pomerium-claim-preferred-username"]) ?? stringClaim(claims ?? {}, ["username", "preferred_username", "user"]);
|
|
101
|
+
const groups = stringArrayHeaderClaim(headerStore, ["x-pomerium-claim-groups", "x-pomerium-claim-roles"]) ?? stringArrayClaim(claims ?? {}, ["groups", "roles"]);
|
|
102
|
+
const role = stringHeaderClaim(headerStore, ["x-pomerium-claim-role"]) ?? stringClaim(claims ?? {}, ["role"]);
|
|
103
|
+
if (!email && !subject && !name) return null;
|
|
104
|
+
return {
|
|
105
|
+
email,
|
|
106
|
+
expiresAt: Number.MAX_SAFE_INTEGER,
|
|
107
|
+
groups,
|
|
108
|
+
isAuthenticated: true,
|
|
109
|
+
name,
|
|
110
|
+
pictureUrl,
|
|
111
|
+
provider: "pomerium",
|
|
112
|
+
rawClaims: claims ?? void 0,
|
|
113
|
+
role,
|
|
114
|
+
subject: subject ?? email,
|
|
115
|
+
username
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function getEdgeSession() {
|
|
119
|
+
const headerStore = await headers();
|
|
120
|
+
const pomerium = parsePomeriumSession(headerStore);
|
|
121
|
+
if (pomerium) return pomerium;
|
|
122
|
+
const cookieSession = await getSessionFromCookies();
|
|
123
|
+
if (cookieSession.isAuthenticated) {
|
|
124
|
+
return { ...cookieSession, provider: "cookie" };
|
|
125
|
+
}
|
|
126
|
+
return { expiresAt: 0, isAuthenticated: false, provider: "anonymous" };
|
|
127
|
+
}
|
|
128
|
+
function getPomeriumAuthenticateUrl(redirectTo, origin) {
|
|
129
|
+
const appUrl = origin ?? process.env.APP_PUBLIC_URL ?? process.env.NEXT_PUBLIC_SITE_URL;
|
|
130
|
+
if (!appUrl) return null;
|
|
131
|
+
const url = new URL(
|
|
132
|
+
process.env.POMERIUM_LOGIN_PATH ?? "/auth/edge/login",
|
|
133
|
+
`${normalizeUrlLike(appUrl)}/`
|
|
134
|
+
);
|
|
135
|
+
url.searchParams.set("pomerium_redirect_uri", buildPublicUrl(redirectTo, origin));
|
|
136
|
+
return url.toString();
|
|
137
|
+
}
|
|
138
|
+
function handleEdgeLogin(request) {
|
|
139
|
+
const url = new URL(request.url);
|
|
140
|
+
const redirectTo = url.searchParams.get("pomerium_redirect_uri") ?? "/";
|
|
141
|
+
redirect(redirectTo);
|
|
142
|
+
}
|
|
143
|
+
function getPomeriumSignOutUrl(redirectTo, origin) {
|
|
144
|
+
const authUrl = process.env.POMERIUM_AUTHENTICATE_URL ?? process.env.POMERIUM_AUTH_URL ?? origin;
|
|
145
|
+
if (!authUrl) return null;
|
|
146
|
+
const url = new URL("/.pomerium/sign_out", `${normalizeUrlLike(authUrl)}/`);
|
|
147
|
+
url.searchParams.set("pomerium_redirect_uri", buildPublicUrl(redirectTo, origin));
|
|
148
|
+
return url.toString();
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
getEdgeSession,
|
|
152
|
+
getPomeriumAuthenticateUrl,
|
|
153
|
+
getPomeriumSignOutUrl,
|
|
154
|
+
handleEdgeLogin
|
|
155
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { OidcClaims, OidcIdentity, OidcVerifyConfig, verifyOidcToken } from './oidc.js';
|
|
2
|
+
export { OidcHumanResolverConfig, createOidcHumanResolver } from './oidc-human.js';
|
|
3
|
+
export { OidcAuthUrlConfig, PkceChallenge, buildOidcAuthUrl, generatePkce } from './pkce.js';
|
|
4
|
+
export { CliAuthResult, CliAuthStart, PollOptions, pollCliAuth, startCliAuth } from './cli-auth.js';
|
|
5
|
+
export { hashToken, looksLikeJwt, stripBearer } from './token.js';
|
|
6
|
+
export { buildAccountProfileUrl, buildAuthLoginUrl, buildAuthLogoutUrl, buildPublicUrl, getAccountPublicUrl, getAppBasePath, getAuthPublicUrl, getPublicSiteUrl, normalizeUrlLike } from './url-helpers.js';
|
|
7
|
+
export { ZitadelClaims, ZitadelConfig, ZitadelIdentity, verifyZitadelToken } from './zitadel.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
verifyZitadelToken
|
|
3
|
+
} from "./chunk-BMPRMOI7.js";
|
|
4
|
+
import {
|
|
5
|
+
createOidcHumanResolver
|
|
6
|
+
} from "./chunk-6TUNNS2B.js";
|
|
7
|
+
import {
|
|
8
|
+
verifyOidcToken
|
|
9
|
+
} from "./chunk-BL74TFCV.js";
|
|
10
|
+
import {
|
|
11
|
+
buildOidcAuthUrl,
|
|
12
|
+
generatePkce
|
|
13
|
+
} from "./chunk-M7EACPIB.js";
|
|
14
|
+
import {
|
|
15
|
+
pollCliAuth,
|
|
16
|
+
startCliAuth
|
|
17
|
+
} from "./chunk-C4V5LCFA.js";
|
|
18
|
+
import {
|
|
19
|
+
hashToken,
|
|
20
|
+
looksLikeJwt,
|
|
21
|
+
stripBearer
|
|
22
|
+
} from "./chunk-VBIQJKUU.js";
|
|
23
|
+
import {
|
|
24
|
+
buildAccountProfileUrl,
|
|
25
|
+
buildAuthLoginUrl,
|
|
26
|
+
buildAuthLogoutUrl,
|
|
27
|
+
buildPublicUrl,
|
|
28
|
+
getAccountPublicUrl,
|
|
29
|
+
getAppBasePath,
|
|
30
|
+
getAuthPublicUrl,
|
|
31
|
+
getPublicSiteUrl,
|
|
32
|
+
normalizeUrlLike
|
|
33
|
+
} from "./chunk-3NJASEF4.js";
|
|
34
|
+
export {
|
|
35
|
+
buildAccountProfileUrl,
|
|
36
|
+
buildAuthLoginUrl,
|
|
37
|
+
buildAuthLogoutUrl,
|
|
38
|
+
buildOidcAuthUrl,
|
|
39
|
+
buildPublicUrl,
|
|
40
|
+
createOidcHumanResolver,
|
|
41
|
+
generatePkce,
|
|
42
|
+
getAccountPublicUrl,
|
|
43
|
+
getAppBasePath,
|
|
44
|
+
getAuthPublicUrl,
|
|
45
|
+
getPublicSiteUrl,
|
|
46
|
+
hashToken,
|
|
47
|
+
looksLikeJwt,
|
|
48
|
+
normalizeUrlLike,
|
|
49
|
+
pollCliAuth,
|
|
50
|
+
startCliAuth,
|
|
51
|
+
stripBearer,
|
|
52
|
+
verifyOidcToken,
|
|
53
|
+
verifyZitadelToken
|
|
54
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { OidcIdentity } from './oidc.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared OIDC → HumanContext resolver.
|
|
5
|
+
*
|
|
6
|
+
* Handles: JWT detection, issuer check, signature verification.
|
|
7
|
+
* DB lookup / user creation is delegated to `findOrCreate` — kept app-specific.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const resolve = createOidcHumanResolver({
|
|
11
|
+
* issuer: env.OIDC_ISSUER,
|
|
12
|
+
* audience: env.OIDC_AUDIENCE,
|
|
13
|
+
* findOrCreate: async (identity) => { /* DB upsert *\/ },
|
|
14
|
+
* })
|
|
15
|
+
* const ctx = await resolve(bearerToken)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface OidcHumanResolverConfig<T> {
|
|
19
|
+
/** OIDC issuer URL — e.g. https://nesskey.com */
|
|
20
|
+
issuer: string;
|
|
21
|
+
/** Optional audience restriction */
|
|
22
|
+
audience?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Called after successful OIDC verification.
|
|
25
|
+
* Return the app's HumanContext or null to reject.
|
|
26
|
+
* Responsible for DB lookup / upsert.
|
|
27
|
+
* rawToken is passed so implementations can call userinfo if needed.
|
|
28
|
+
*/
|
|
29
|
+
findOrCreate: (identity: OidcIdentity, rawToken: string) => Promise<T | null>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns a resolver function `(token: string) => Promise<T | null>`.
|
|
33
|
+
* Call it with a raw Bearer token (JWT form). Returns null if:
|
|
34
|
+
* - token is not a JWT
|
|
35
|
+
* - issuer doesn't match
|
|
36
|
+
* - signature invalid / expired
|
|
37
|
+
* - findOrCreate returns null
|
|
38
|
+
*/
|
|
39
|
+
declare function createOidcHumanResolver<T>(config: OidcHumanResolverConfig<T>): (token: string) => Promise<T | null>;
|
|
40
|
+
|
|
41
|
+
export { type OidcHumanResolverConfig, OidcIdentity, createOidcHumanResolver };
|
package/dist/oidc.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface OidcVerifyConfig {
|
|
2
|
+
issuer: string;
|
|
3
|
+
audience?: string;
|
|
4
|
+
}
|
|
5
|
+
interface OidcClaims {
|
|
6
|
+
sub: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
preferred_username?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
picture?: string;
|
|
11
|
+
exp?: number;
|
|
12
|
+
iss?: string;
|
|
13
|
+
aud?: string | string[];
|
|
14
|
+
}
|
|
15
|
+
interface OidcIdentity {
|
|
16
|
+
subject: string;
|
|
17
|
+
issuer: string;
|
|
18
|
+
email: string;
|
|
19
|
+
name: string;
|
|
20
|
+
picture?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify an OIDC JWT and return the normalised identity.
|
|
24
|
+
* Uses JWKS from `{issuer}/oauth/v2/keys` (Zitadel-compatible endpoint).
|
|
25
|
+
* Returns null on any verification failure — never throws to the caller.
|
|
26
|
+
*/
|
|
27
|
+
declare function verifyOidcToken(token: string, config: OidcVerifyConfig): Promise<OidcIdentity | null>;
|
|
28
|
+
|
|
29
|
+
export { type OidcClaims, type OidcIdentity, type OidcVerifyConfig, verifyOidcToken };
|
package/dist/oidc.js
ADDED
package/dist/pkce.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) utilities — RFC 7636.
|
|
3
|
+
* Runtime-agnostic: Workers, Node 20+, browser (Web Crypto API).
|
|
4
|
+
*/
|
|
5
|
+
interface PkceChallenge {
|
|
6
|
+
verifier: string;
|
|
7
|
+
challenge: string;
|
|
8
|
+
method: 'S256';
|
|
9
|
+
}
|
|
10
|
+
interface OidcAuthUrlConfig {
|
|
11
|
+
/** OIDC issuer base URL, e.g. "https://nesskey.com" */
|
|
12
|
+
issuer: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
/** Defaults to ["openid", "email", "profile"] */
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
state?: string;
|
|
18
|
+
challenge: string;
|
|
19
|
+
/**
|
|
20
|
+
* OIDC prompt parameter.
|
|
21
|
+
* - "select_account" → show account chooser even if session exists (recommended for web apps)
|
|
22
|
+
* - "login" → force re-authentication every time
|
|
23
|
+
* - "none" → silent auth, error if no session
|
|
24
|
+
*/
|
|
25
|
+
prompt?: 'select_account' | 'login' | 'none' | 'consent';
|
|
26
|
+
}
|
|
27
|
+
/** Generate a PKCE code_verifier + S256 code_challenge pair. */
|
|
28
|
+
declare function generatePkce(): Promise<PkceChallenge>;
|
|
29
|
+
/**
|
|
30
|
+
* Build an OIDC authorization URL with PKCE.
|
|
31
|
+
* Compatible with any OIDC provider (Zitadel, Auth0, Keycloak, etc.).
|
|
32
|
+
*/
|
|
33
|
+
declare function buildOidcAuthUrl(config: OidcAuthUrlConfig): string;
|
|
34
|
+
|
|
35
|
+
export { type OidcAuthUrlConfig, type PkceChallenge, buildOidcAuthUrl, generatePkce };
|
package/dist/pkce.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
type OidcSession = {
|
|
4
|
+
audience?: string | string[];
|
|
5
|
+
email?: string;
|
|
6
|
+
expiresAt: number;
|
|
7
|
+
isAuthenticated: boolean;
|
|
8
|
+
issuer?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
pictureUrl?: string;
|
|
11
|
+
subject?: string;
|
|
12
|
+
tokenIdentifier?: string;
|
|
13
|
+
};
|
|
14
|
+
declare const oidcCookies: {
|
|
15
|
+
readonly codeVerifier: "oidc_code_verifier";
|
|
16
|
+
readonly state: "oidc_state";
|
|
17
|
+
readonly nonce: "oidc_nonce";
|
|
18
|
+
readonly returnTo: "oidc_return_to";
|
|
19
|
+
readonly idToken: "oidc_id_token";
|
|
20
|
+
readonly accessToken: "oidc_access_token";
|
|
21
|
+
readonly refreshToken: "oidc_refresh_token";
|
|
22
|
+
readonly expiresAt: "oidc_expires_at";
|
|
23
|
+
readonly session: "oidc_session";
|
|
24
|
+
};
|
|
25
|
+
declare function buildAuthorizationRedirect(request: NextRequest): Promise<NextResponse<unknown>>;
|
|
26
|
+
declare function handleAuthorizationCallback(request: NextRequest): Promise<NextResponse<unknown>>;
|
|
27
|
+
declare function getSessionFromCookies(): Promise<OidcSession>;
|
|
28
|
+
declare function getServerIdToken(): Promise<string | null>;
|
|
29
|
+
declare function hasServerOidcSession(): Promise<boolean>;
|
|
30
|
+
declare function getServerAccessToken(): Promise<string | null>;
|
|
31
|
+
declare function buildSessionResponse(_request?: NextRequest): Promise<NextResponse<{
|
|
32
|
+
expiresAt: number;
|
|
33
|
+
isAuthenticated: false;
|
|
34
|
+
}> | NextResponse<OidcSession>>;
|
|
35
|
+
declare function buildTokenResponse(request: NextRequest): Promise<NextResponse<{
|
|
36
|
+
token: null;
|
|
37
|
+
}> | NextResponse<{
|
|
38
|
+
token: string;
|
|
39
|
+
}>>;
|
|
40
|
+
declare function buildLogoutResponse(request: NextRequest): Promise<NextResponse<unknown>>;
|
|
41
|
+
|
|
42
|
+
export { type OidcSession, buildAuthorizationRedirect, buildLogoutResponse, buildSessionResponse, buildTokenResponse, getServerAccessToken, getServerIdToken, getSessionFromCookies, handleAuthorizationCallback, hasServerOidcSession, oidcCookies };
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildAuthorizationRedirect,
|
|
3
|
+
buildLogoutResponse,
|
|
4
|
+
buildSessionResponse,
|
|
5
|
+
buildTokenResponse,
|
|
6
|
+
getServerAccessToken,
|
|
7
|
+
getServerIdToken,
|
|
8
|
+
getSessionFromCookies,
|
|
9
|
+
handleAuthorizationCallback,
|
|
10
|
+
hasServerOidcSession,
|
|
11
|
+
oidcCookies
|
|
12
|
+
} from "./chunk-VUB4GTMI.js";
|
|
13
|
+
import "./chunk-3NJASEF4.js";
|
|
14
|
+
export {
|
|
15
|
+
buildAuthorizationRedirect,
|
|
16
|
+
buildLogoutResponse,
|
|
17
|
+
buildSessionResponse,
|
|
18
|
+
buildTokenResponse,
|
|
19
|
+
getServerAccessToken,
|
|
20
|
+
getServerIdToken,
|
|
21
|
+
getSessionFromCookies,
|
|
22
|
+
handleAuthorizationCallback,
|
|
23
|
+
hasServerOidcSession,
|
|
24
|
+
oidcCookies
|
|
25
|
+
};
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token hashing utilities — no libsodium dependency, Web Crypto only.
|
|
3
|
+
* Runtime-agnostic: Workers, Node 20+, browser.
|
|
4
|
+
*/
|
|
5
|
+
/** HMAC-SHA-256 hash with optional pepper. Use for API key storage at rest. */
|
|
6
|
+
declare function hashToken(token: string, pepper?: string): Promise<string>;
|
|
7
|
+
declare function looksLikeJwt(token: string): boolean;
|
|
8
|
+
declare function stripBearer(header: string | null | undefined): string | null;
|
|
9
|
+
|
|
10
|
+
export { hashToken, looksLikeJwt, stripBearer };
|
package/dist/token.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare function normalizeUrlLike(value: string): string;
|
|
2
|
+
declare function getAuthPublicUrl(): string;
|
|
3
|
+
declare function getAccountPublicUrl(): string;
|
|
4
|
+
declare function getAppBasePath(defaultPath?: string): string;
|
|
5
|
+
declare function getPublicSiteUrl(): string;
|
|
6
|
+
declare function buildPublicUrl(pathOrUrl: string, fallbackOrigin?: string): string;
|
|
7
|
+
declare function buildAuthLoginUrl(redirectTo: string, origin?: string): string;
|
|
8
|
+
declare function buildAuthLogoutUrl(redirectTo: string, origin?: string): string;
|
|
9
|
+
declare function buildAccountProfileUrl(): string;
|
|
10
|
+
|
|
11
|
+
export { buildAccountProfileUrl, buildAuthLoginUrl, buildAuthLogoutUrl, buildPublicUrl, getAccountPublicUrl, getAppBasePath, getAuthPublicUrl, getPublicSiteUrl, normalizeUrlLike };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildAccountProfileUrl,
|
|
3
|
+
buildAuthLoginUrl,
|
|
4
|
+
buildAuthLogoutUrl,
|
|
5
|
+
buildPublicUrl,
|
|
6
|
+
getAccountPublicUrl,
|
|
7
|
+
getAppBasePath,
|
|
8
|
+
getAuthPublicUrl,
|
|
9
|
+
getPublicSiteUrl,
|
|
10
|
+
normalizeUrlLike
|
|
11
|
+
} from "./chunk-3NJASEF4.js";
|
|
12
|
+
export {
|
|
13
|
+
buildAccountProfileUrl,
|
|
14
|
+
buildAuthLoginUrl,
|
|
15
|
+
buildAuthLogoutUrl,
|
|
16
|
+
buildPublicUrl,
|
|
17
|
+
getAccountPublicUrl,
|
|
18
|
+
getAppBasePath,
|
|
19
|
+
getAuthPublicUrl,
|
|
20
|
+
getPublicSiteUrl,
|
|
21
|
+
normalizeUrlLike
|
|
22
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface ZitadelConfig {
|
|
2
|
+
issuer: string;
|
|
3
|
+
audience?: string;
|
|
4
|
+
}
|
|
5
|
+
interface ZitadelClaims {
|
|
6
|
+
sub: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
preferred_username?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
picture?: string;
|
|
11
|
+
exp?: number;
|
|
12
|
+
iss?: string;
|
|
13
|
+
aud?: string | string[];
|
|
14
|
+
}
|
|
15
|
+
interface ZitadelIdentity {
|
|
16
|
+
subject: string;
|
|
17
|
+
issuer: string;
|
|
18
|
+
email: string;
|
|
19
|
+
name: string;
|
|
20
|
+
picture?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify a Zitadel JWT and return the identity.
|
|
24
|
+
* Returns null on any verification failure — never throws to the caller.
|
|
25
|
+
*/
|
|
26
|
+
declare function verifyZitadelToken(token: string, config: ZitadelConfig): Promise<ZitadelIdentity | null>;
|
|
27
|
+
|
|
28
|
+
export { type ZitadelClaims, type ZitadelConfig, type ZitadelIdentity, verifyZitadelToken };
|
package/dist/zitadel.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@baseworks/auth",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dist/index.js",
|
|
7
|
+
"./oidc": "./dist/oidc.js",
|
|
8
|
+
"./oidc-human": "./dist/oidc-human.js",
|
|
9
|
+
"./pkce": "./dist/pkce.js",
|
|
10
|
+
"./cli-auth": "./dist/cli-auth.js",
|
|
11
|
+
"./token": "./dist/token.js",
|
|
12
|
+
"./session": "./dist/session.js",
|
|
13
|
+
"./url-helpers": "./dist/url-helpers.js",
|
|
14
|
+
"./edge": "./dist/edge.js",
|
|
15
|
+
"./zitadel": "./dist/zitadel.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@baseworks/core": "^0.2.0",
|
|
27
|
+
"jose": "^6.2.3"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"next": ">=15.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"next": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@baseworks/tsconfig": "workspace:*",
|
|
39
|
+
"@types/node": "^26.0.1",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^5.6.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli-auth.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI authentication helpers — PKCE polling flow.
|
|
3
|
+
* Used by any CLI tool that authenticates via an orbseal-compatible API.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. startCliAuth() → get state + URL to show user
|
|
7
|
+
* 2. pollCliAuth() → long-poll until user completes browser auth
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface CliAuthStart {
|
|
11
|
+
state: string;
|
|
12
|
+
url: string;
|
|
13
|
+
expiresIn: number; // seconds
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CliAuthResult {
|
|
17
|
+
token: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PollOptions {
|
|
21
|
+
/** ms between polls (default: 2000) */
|
|
22
|
+
intervalMs?: number;
|
|
23
|
+
/** ms before giving up (default: 300_000 = 5 min) */
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Call the API to initiate CLI login.
|
|
29
|
+
* Returns the state handle and the URL the user should open.
|
|
30
|
+
*/
|
|
31
|
+
export async function startCliAuth(apiBase: string): Promise<CliAuthStart> {
|
|
32
|
+
const url = `${apiBase.replace(/\/+$/, '')}/v1/auth/start`;
|
|
33
|
+
const res = await fetch(url);
|
|
34
|
+
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const text = await res.text().catch(() => res.statusText);
|
|
37
|
+
throw new Error(`Auth start failed (${res.status}): ${text}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const data = await res.json() as {
|
|
41
|
+
state?: string;
|
|
42
|
+
url?: string;
|
|
43
|
+
expires_in?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (!data.state || !data.url) {
|
|
47
|
+
throw new Error('Invalid response from auth/start');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
state: data.state,
|
|
52
|
+
url: data.url,
|
|
53
|
+
expiresIn: data.expires_in ?? 600,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Poll the API until the user completes browser auth.
|
|
59
|
+
* Resolves with the token when done, rejects on timeout or error.
|
|
60
|
+
*/
|
|
61
|
+
export async function pollCliAuth(
|
|
62
|
+
apiBase: string,
|
|
63
|
+
state: string,
|
|
64
|
+
opts: PollOptions = {},
|
|
65
|
+
): Promise<CliAuthResult> {
|
|
66
|
+
const intervalMs = opts.intervalMs ?? 2_000;
|
|
67
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
68
|
+
const base = apiBase.replace(/\/+$/, '');
|
|
69
|
+
const deadline = Date.now() + timeoutMs;
|
|
70
|
+
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
await sleep(intervalMs);
|
|
73
|
+
|
|
74
|
+
const res = await fetch(`${base}/v1/auth/poll/${state}`);
|
|
75
|
+
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(`Poll failed (${res.status})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data = await res.json() as {
|
|
81
|
+
status?: string;
|
|
82
|
+
token?: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (data.status === 'done' && data.token) {
|
|
86
|
+
return { token: data.token };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (data.status === 'expired') {
|
|
90
|
+
throw new Error('Auth session expired — run login again');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// status === "pending" → continue polling
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw new Error('Auth timed out — run login again');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
function sleep(ms: number): Promise<void> {
|
|
102
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
103
|
+
}
|