@edcalderon/auth 1.2.2 → 1.4.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/CHANGELOG.md +23 -0
- package/README.md +73 -7
- package/dist/AuthentikOidcClient.d.ts +58 -0
- package/dist/AuthentikOidcClient.js +284 -0
- package/dist/authentik/callback.d.ts +71 -0
- package/dist/authentik/callback.js +163 -0
- package/dist/authentik/config.d.ts +53 -0
- package/dist/authentik/config.js +169 -0
- package/dist/authentik/index.d.ts +17 -0
- package/dist/authentik/index.js +22 -0
- package/dist/authentik/logout.d.ts +50 -0
- package/dist/authentik/logout.js +96 -0
- package/dist/authentik/provisioning.d.ts +124 -0
- package/dist/authentik/provisioning.js +342 -0
- package/dist/authentik/redirect.d.ts +20 -0
- package/dist/authentik/redirect.js +52 -0
- package/dist/authentik/relay.d.ts +48 -0
- package/dist/authentik/relay.js +146 -0
- package/dist/authentik/types.d.ts +264 -0
- package/dist/authentik/types.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +19 -1
- package/supabase/migrations/003_authentik_shadow_auth_users.sql +81 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Authentik callback handler with blocking provisioning support.
|
|
3
|
+
*
|
|
4
|
+
* This module handles the OIDC authorization code exchange and optionally
|
|
5
|
+
* calls a provisioning adapter **before** allowing the app to redirect
|
|
6
|
+
* into the protected product.
|
|
7
|
+
*
|
|
8
|
+
* Reference: CIG apps/dashboard/app/auth/callback/page.tsx
|
|
9
|
+
*/
|
|
10
|
+
/* ------------------------------------------------------------------ */
|
|
11
|
+
/* Token exchange */
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
/**
|
|
14
|
+
* Exchange an authorization code for tokens using PKCE.
|
|
15
|
+
*
|
|
16
|
+
* This is a pure server-safe function — it does not touch sessionStorage.
|
|
17
|
+
*/
|
|
18
|
+
export async function exchangeCode(config, code, codeVerifier) {
|
|
19
|
+
const tokenUrl = config.tokenEndpoint;
|
|
20
|
+
const fetchFn = config.fetchFn || fetch;
|
|
21
|
+
const body = new URLSearchParams({
|
|
22
|
+
grant_type: "authorization_code",
|
|
23
|
+
code,
|
|
24
|
+
redirect_uri: config.redirectUri,
|
|
25
|
+
client_id: config.clientId,
|
|
26
|
+
code_verifier: codeVerifier,
|
|
27
|
+
});
|
|
28
|
+
const response = await fetchFn(tokenUrl, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
31
|
+
body,
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`NETWORK_ERROR: Token exchange failed (HTTP ${response.status})`);
|
|
35
|
+
}
|
|
36
|
+
const json = (await response.json());
|
|
37
|
+
if (!json.access_token || typeof json.access_token !== "string") {
|
|
38
|
+
throw new Error("SESSION_ERROR: Token response missing access_token");
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
access_token: json.access_token,
|
|
42
|
+
token_type: json.token_type ?? undefined,
|
|
43
|
+
refresh_token: json.refresh_token ?? undefined,
|
|
44
|
+
id_token: json.id_token ?? undefined,
|
|
45
|
+
expires_in: typeof json.expires_in === "number" ? json.expires_in : undefined,
|
|
46
|
+
scope: json.scope ?? undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/* ------------------------------------------------------------------ */
|
|
50
|
+
/* Userinfo */
|
|
51
|
+
/* ------------------------------------------------------------------ */
|
|
52
|
+
/**
|
|
53
|
+
* Fetch OIDC claims from the Authentik userinfo endpoint.
|
|
54
|
+
*/
|
|
55
|
+
export async function fetchClaims(config, accessToken) {
|
|
56
|
+
const userinfoUrl = config.userinfoEndpoint;
|
|
57
|
+
const fetchFn = config.fetchFn || fetch;
|
|
58
|
+
const response = await fetchFn(userinfoUrl, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`NETWORK_ERROR: Userinfo request failed (HTTP ${response.status})`);
|
|
64
|
+
}
|
|
65
|
+
const claims = (await response.json());
|
|
66
|
+
if (!claims.sub || !claims.iss) {
|
|
67
|
+
throw new Error("SESSION_ERROR: Userinfo response missing required claims (sub, iss)");
|
|
68
|
+
}
|
|
69
|
+
return claims;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Process an Authentik OIDC callback end-to-end:
|
|
73
|
+
*
|
|
74
|
+
* 1. Validate state matches
|
|
75
|
+
* 2. Exchange the authorization code for tokens
|
|
76
|
+
* 3. Fetch OIDC claims from userinfo
|
|
77
|
+
* 4. (Optional) Run the provisioning adapter — blocks until complete
|
|
78
|
+
* 5. Return the combined result
|
|
79
|
+
*
|
|
80
|
+
* If any step fails the function returns `{ success: false, error }`.
|
|
81
|
+
* It does **not** throw so that callers can present structured error UI.
|
|
82
|
+
*/
|
|
83
|
+
export async function processCallback(options) {
|
|
84
|
+
// 1. State validation
|
|
85
|
+
if (options.state !== options.expectedState) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: "Invalid callback state — possible CSRF or expired session",
|
|
89
|
+
errorCode: "state_mismatch",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// 2. Token exchange
|
|
93
|
+
let tokens;
|
|
94
|
+
try {
|
|
95
|
+
tokens = await exchangeCode(options.config, options.code, options.codeVerifier);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: err instanceof Error ? err.message : "Token exchange failed",
|
|
101
|
+
errorCode: "token_exchange_failed",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// 3. Fetch claims
|
|
105
|
+
let claims;
|
|
106
|
+
try {
|
|
107
|
+
claims = await fetchClaims(options.config, tokens.access_token);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: err instanceof Error ? err.message : "Userinfo fetch failed",
|
|
113
|
+
errorCode: "userinfo_failed",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const callbackResult = {
|
|
117
|
+
tokens,
|
|
118
|
+
claims,
|
|
119
|
+
provider: options.provider,
|
|
120
|
+
};
|
|
121
|
+
// 4. Provisioning gate
|
|
122
|
+
if (options.provisioningAdapter) {
|
|
123
|
+
const payload = {
|
|
124
|
+
sub: claims.sub,
|
|
125
|
+
iss: claims.iss,
|
|
126
|
+
email: (claims.email || "").toLowerCase(),
|
|
127
|
+
emailVerified: claims.email_verified,
|
|
128
|
+
name: claims.name || claims.preferred_username,
|
|
129
|
+
picture: claims.picture,
|
|
130
|
+
provider: options.provider,
|
|
131
|
+
rawClaims: claims,
|
|
132
|
+
};
|
|
133
|
+
let provisioningResult;
|
|
134
|
+
try {
|
|
135
|
+
provisioningResult = await options.provisioningAdapter.sync(payload);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
callbackResult,
|
|
141
|
+
error: err instanceof Error ? err.message : "Provisioning failed",
|
|
142
|
+
errorCode: "provisioning_error",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (!provisioningResult.synced) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
callbackResult,
|
|
149
|
+
provisioningResult,
|
|
150
|
+
error: provisioningResult.error || "User provisioning did not complete",
|
|
151
|
+
errorCode: provisioningResult.errorCode || "provisioning_failed",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
callbackResult,
|
|
157
|
+
provisioningResult,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// No adapter — exchange-only mode
|
|
161
|
+
return { success: true, callbackResult };
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration validation / doctor helpers.
|
|
3
|
+
*
|
|
4
|
+
* These helpers detect missing or invalid configuration **before** the
|
|
5
|
+
* first real user login, so misconfigured environments fail fast.
|
|
6
|
+
*
|
|
7
|
+
* Reference: CIG `supabase_not_configured` callback error code.
|
|
8
|
+
*/
|
|
9
|
+
import type { ConfigValidationResult, AuthentikCallbackConfig, AuthentikEndpoints, SupabaseSyncConfig } from "./types";
|
|
10
|
+
/**
|
|
11
|
+
* Validate that the core Authentik OIDC configuration is present and
|
|
12
|
+
* syntactically correct.
|
|
13
|
+
*
|
|
14
|
+
* This does **not** make network requests — it only checks that the
|
|
15
|
+
* required values are set and look reasonable.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateAuthentikConfig(config: Partial<AuthentikCallbackConfig>): ConfigValidationResult;
|
|
18
|
+
/**
|
|
19
|
+
* Validate the server-side Supabase sync configuration.
|
|
20
|
+
*
|
|
21
|
+
* Returns a diagnostic result with the exact error code
|
|
22
|
+
* `supabase_not_configured` when the Supabase URL or service role key
|
|
23
|
+
* is missing, matching the CIG convention.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateSupabaseSyncConfig(config: Partial<SupabaseSyncConfig>): ConfigValidationResult;
|
|
26
|
+
/**
|
|
27
|
+
* Validate both Authentik and Supabase sync configuration in one call.
|
|
28
|
+
*
|
|
29
|
+
* Useful as a startup / health-check / deploy-time validation gate.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateFullConfig(authentikConfig: Partial<AuthentikCallbackConfig>, supabaseConfig: Partial<SupabaseSyncConfig>): ConfigValidationResult;
|
|
32
|
+
/**
|
|
33
|
+
* Discover OIDC endpoint URLs from an Authentik issuer's
|
|
34
|
+
* `.well-known/openid-configuration`.
|
|
35
|
+
*
|
|
36
|
+
* **Important:** Authentik places most OIDC endpoints (token, userinfo,
|
|
37
|
+
* authorize, revocation) at the `/application/o/` level, *not* under the
|
|
38
|
+
* per-app issuer path. For example, with:
|
|
39
|
+
* issuer = `https://auth.example.com/application/o/my-app/`
|
|
40
|
+
* the token endpoint is:
|
|
41
|
+
* `https://auth.example.com/application/o/token/`
|
|
42
|
+
*
|
|
43
|
+
* This function fetches the correct endpoint URLs from the well-known
|
|
44
|
+
* document so callers never need to guess.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* const endpoints = await discoverEndpoints("https://auth.example.com/application/o/my-app/");
|
|
48
|
+
* // endpoints.token → "https://auth.example.com/application/o/token/"
|
|
49
|
+
* // endpoints.userinfo → "https://auth.example.com/application/o/userinfo/"
|
|
50
|
+
* // endpoints.endSession → "https://auth.example.com/application/o/my-app/end-session/"
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function discoverEndpoints(issuer: string, fetchFn?: typeof fetch): Promise<AuthentikEndpoints>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration validation / doctor helpers.
|
|
3
|
+
*
|
|
4
|
+
* These helpers detect missing or invalid configuration **before** the
|
|
5
|
+
* first real user login, so misconfigured environments fail fast.
|
|
6
|
+
*
|
|
7
|
+
* Reference: CIG `supabase_not_configured` callback error code.
|
|
8
|
+
*/
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Authentik config validation */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
/**
|
|
13
|
+
* Validate that the core Authentik OIDC configuration is present and
|
|
14
|
+
* syntactically correct.
|
|
15
|
+
*
|
|
16
|
+
* This does **not** make network requests — it only checks that the
|
|
17
|
+
* required values are set and look reasonable.
|
|
18
|
+
*/
|
|
19
|
+
export function validateAuthentikConfig(config) {
|
|
20
|
+
const checks = [];
|
|
21
|
+
// issuer
|
|
22
|
+
checks.push(config.issuer
|
|
23
|
+
? isValidUrl(config.issuer)
|
|
24
|
+
? { name: "issuer", passed: true, message: "Issuer URL is valid", severity: "error" }
|
|
25
|
+
: { name: "issuer", passed: false, message: `Issuer is not a valid URL: ${config.issuer}`, severity: "error" }
|
|
26
|
+
: { name: "issuer", passed: false, message: "Authentik issuer URL is required", severity: "error" });
|
|
27
|
+
// clientId
|
|
28
|
+
checks.push(config.clientId
|
|
29
|
+
? { name: "clientId", passed: true, message: "Client ID is set", severity: "error" }
|
|
30
|
+
: { name: "clientId", passed: false, message: "Authentik client_id is required", severity: "error" });
|
|
31
|
+
// redirectUri
|
|
32
|
+
checks.push(config.redirectUri
|
|
33
|
+
? isValidUrl(config.redirectUri)
|
|
34
|
+
? { name: "redirectUri", passed: true, message: "Redirect URI is valid", severity: "error" }
|
|
35
|
+
: { name: "redirectUri", passed: false, message: `Redirect URI is not a valid URL: ${config.redirectUri}`, severity: "error" }
|
|
36
|
+
: { name: "redirectUri", passed: false, message: "Redirect URI is required", severity: "error" });
|
|
37
|
+
// tokenEndpoint
|
|
38
|
+
checks.push(config.tokenEndpoint
|
|
39
|
+
? isValidUrl(config.tokenEndpoint)
|
|
40
|
+
? { name: "tokenEndpoint", passed: true, message: "Token endpoint URL is valid", severity: "error" }
|
|
41
|
+
: { name: "tokenEndpoint", passed: false, message: `Token endpoint is not a valid URL: ${config.tokenEndpoint}`, severity: "error" }
|
|
42
|
+
: { name: "tokenEndpoint", passed: false, message: "Token endpoint URL is required — use discoverEndpoints() or supply manually", severity: "error" });
|
|
43
|
+
// userinfoEndpoint
|
|
44
|
+
checks.push(config.userinfoEndpoint
|
|
45
|
+
? isValidUrl(config.userinfoEndpoint)
|
|
46
|
+
? { name: "userinfoEndpoint", passed: true, message: "Userinfo endpoint URL is valid", severity: "error" }
|
|
47
|
+
: { name: "userinfoEndpoint", passed: false, message: `Userinfo endpoint is not a valid URL: ${config.userinfoEndpoint}`, severity: "error" }
|
|
48
|
+
: { name: "userinfoEndpoint", passed: false, message: "Userinfo endpoint URL is required — use discoverEndpoints() or supply manually", severity: "error" });
|
|
49
|
+
return {
|
|
50
|
+
valid: checks.every((c) => c.passed),
|
|
51
|
+
checks,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/* ------------------------------------------------------------------ */
|
|
55
|
+
/* Supabase sync config validation */
|
|
56
|
+
/* ------------------------------------------------------------------ */
|
|
57
|
+
/**
|
|
58
|
+
* Validate the server-side Supabase sync configuration.
|
|
59
|
+
*
|
|
60
|
+
* Returns a diagnostic result with the exact error code
|
|
61
|
+
* `supabase_not_configured` when the Supabase URL or service role key
|
|
62
|
+
* is missing, matching the CIG convention.
|
|
63
|
+
*/
|
|
64
|
+
export function validateSupabaseSyncConfig(config) {
|
|
65
|
+
const checks = [];
|
|
66
|
+
// supabaseUrl
|
|
67
|
+
checks.push(config.supabaseUrl
|
|
68
|
+
? isValidUrl(config.supabaseUrl)
|
|
69
|
+
? { name: "supabaseUrl", passed: true, message: "Supabase URL is valid", severity: "error" }
|
|
70
|
+
: { name: "supabaseUrl", passed: false, message: `Supabase URL is not a valid URL: ${config.supabaseUrl}`, severity: "error" }
|
|
71
|
+
: { name: "supabaseUrl", passed: false, message: "supabase_not_configured: Supabase URL is required for sync", severity: "error" });
|
|
72
|
+
// supabaseServiceRoleKey
|
|
73
|
+
checks.push(config.supabaseServiceRoleKey
|
|
74
|
+
? config.supabaseServiceRoleKey.length >= 20
|
|
75
|
+
? { name: "supabaseServiceRoleKey", passed: true, message: "Service role key is set", severity: "error" }
|
|
76
|
+
: { name: "supabaseServiceRoleKey", passed: false, message: "Service role key appears too short", severity: "warning" }
|
|
77
|
+
: { name: "supabaseServiceRoleKey", passed: false, message: "supabase_not_configured: Supabase service_role key is required for sync", severity: "error" });
|
|
78
|
+
// upsertRpcName (optional, warn if customised)
|
|
79
|
+
if (config.upsertRpcName && config.upsertRpcName !== "upsert_oidc_user") {
|
|
80
|
+
checks.push({
|
|
81
|
+
name: "upsertRpcName",
|
|
82
|
+
passed: true,
|
|
83
|
+
message: `Custom RPC name: ${config.upsertRpcName}`,
|
|
84
|
+
severity: "warning",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
valid: checks.filter((c) => c.severity === "error").every((c) => c.passed),
|
|
89
|
+
checks,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/* ------------------------------------------------------------------ */
|
|
93
|
+
/* Combined validation */
|
|
94
|
+
/* ------------------------------------------------------------------ */
|
|
95
|
+
/**
|
|
96
|
+
* Validate both Authentik and Supabase sync configuration in one call.
|
|
97
|
+
*
|
|
98
|
+
* Useful as a startup / health-check / deploy-time validation gate.
|
|
99
|
+
*/
|
|
100
|
+
export function validateFullConfig(authentikConfig, supabaseConfig) {
|
|
101
|
+
const authentik = validateAuthentikConfig(authentikConfig);
|
|
102
|
+
const supabase = validateSupabaseSyncConfig(supabaseConfig);
|
|
103
|
+
const checks = [...authentik.checks, ...supabase.checks];
|
|
104
|
+
return {
|
|
105
|
+
valid: checks.filter((c) => c.severity === "error").every((c) => c.passed),
|
|
106
|
+
checks,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/* ------------------------------------------------------------------ */
|
|
110
|
+
/* Helpers */
|
|
111
|
+
/* ------------------------------------------------------------------ */
|
|
112
|
+
function isValidUrl(value) {
|
|
113
|
+
try {
|
|
114
|
+
new URL(value);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/* ------------------------------------------------------------------ */
|
|
122
|
+
/* Endpoint discovery */
|
|
123
|
+
/* ------------------------------------------------------------------ */
|
|
124
|
+
/**
|
|
125
|
+
* Discover OIDC endpoint URLs from an Authentik issuer's
|
|
126
|
+
* `.well-known/openid-configuration`.
|
|
127
|
+
*
|
|
128
|
+
* **Important:** Authentik places most OIDC endpoints (token, userinfo,
|
|
129
|
+
* authorize, revocation) at the `/application/o/` level, *not* under the
|
|
130
|
+
* per-app issuer path. For example, with:
|
|
131
|
+
* issuer = `https://auth.example.com/application/o/my-app/`
|
|
132
|
+
* the token endpoint is:
|
|
133
|
+
* `https://auth.example.com/application/o/token/`
|
|
134
|
+
*
|
|
135
|
+
* This function fetches the correct endpoint URLs from the well-known
|
|
136
|
+
* document so callers never need to guess.
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* const endpoints = await discoverEndpoints("https://auth.example.com/application/o/my-app/");
|
|
140
|
+
* // endpoints.token → "https://auth.example.com/application/o/token/"
|
|
141
|
+
* // endpoints.userinfo → "https://auth.example.com/application/o/userinfo/"
|
|
142
|
+
* // endpoints.endSession → "https://auth.example.com/application/o/my-app/end-session/"
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export async function discoverEndpoints(issuer, fetchFn = fetch) {
|
|
146
|
+
const base = issuer.endsWith("/") ? issuer : `${issuer}/`;
|
|
147
|
+
const wellKnownUrl = `${base}.well-known/openid-configuration`;
|
|
148
|
+
const response = await fetchFn(wellKnownUrl);
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`Failed to fetch .well-known/openid-configuration from ${wellKnownUrl} (HTTP ${response.status})`);
|
|
151
|
+
}
|
|
152
|
+
const doc = (await response.json());
|
|
153
|
+
const authorization = doc.authorization_endpoint;
|
|
154
|
+
const token = doc.token_endpoint;
|
|
155
|
+
const userinfo = doc.userinfo_endpoint;
|
|
156
|
+
if (typeof authorization !== "string" ||
|
|
157
|
+
typeof token !== "string" ||
|
|
158
|
+
typeof userinfo !== "string") {
|
|
159
|
+
throw new Error("Well-known document missing required endpoints (authorization_endpoint, token_endpoint, userinfo_endpoint)");
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
authorization,
|
|
163
|
+
token,
|
|
164
|
+
userinfo,
|
|
165
|
+
revocation: typeof doc.revocation_endpoint === "string" ? doc.revocation_endpoint : undefined,
|
|
166
|
+
endSession: typeof doc.end_session_endpoint === "string" ? doc.end_session_endpoint : undefined,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @edcalderon/auth — Authentik flow + provisioning kit.
|
|
3
|
+
*
|
|
4
|
+
* Barrel export that assembles all Authentik-specific modules into a
|
|
5
|
+
* single importable surface area.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { processCallback, orchestrateLogout, ... } from "@edcalderon/auth/authentik";
|
|
9
|
+
*/
|
|
10
|
+
export type { AuthentikProvider, AuthentikEndpoints, AuthentikRelayConfig, RelayIncomingParams, RelayHandlerResult, AuthentikCallbackConfig, AuthentikTokenResponse, AuthentikClaims, AuthentikCallbackResult, AuthentikLogoutConfig, AuthentikLogoutResult, ProvisioningPayload, ProvisioningResult, ProvisioningAdapter, SupabaseSyncConfig, ConfigValidationResult, ConfigCheck, SafeRedirectConfig, } from "./types";
|
|
11
|
+
export { createRelayPageHtml, parseRelayParams, readRelayStorage, clearRelayStorage, } from "./relay";
|
|
12
|
+
export { exchangeCode, fetchClaims, processCallback, } from "./callback";
|
|
13
|
+
export type { ProcessCallbackOptions, ProcessCallbackResult, } from "./callback";
|
|
14
|
+
export { revokeToken, buildEndSessionUrl, orchestrateLogout, } from "./logout";
|
|
15
|
+
export { NoopProvisioningAdapter, createProvisioningAdapter, normalizePayload, SupabaseSyncAdapter, createSupabaseSyncAdapter, } from "./provisioning";
|
|
16
|
+
export { validateAuthentikConfig, validateSupabaseSyncConfig, validateFullConfig, discoverEndpoints, } from "./config";
|
|
17
|
+
export { resolveSafeRedirect } from "./redirect";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @edcalderon/auth — Authentik flow + provisioning kit.
|
|
3
|
+
*
|
|
4
|
+
* Barrel export that assembles all Authentik-specific modules into a
|
|
5
|
+
* single importable surface area.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { processCallback, orchestrateLogout, ... } from "@edcalderon/auth/authentik";
|
|
9
|
+
*/
|
|
10
|
+
// Relay
|
|
11
|
+
export { createRelayPageHtml, parseRelayParams, readRelayStorage, clearRelayStorage, } from "./relay";
|
|
12
|
+
// Callback
|
|
13
|
+
export { exchangeCode, fetchClaims, processCallback, } from "./callback";
|
|
14
|
+
// Logout
|
|
15
|
+
export { revokeToken, buildEndSessionUrl, orchestrateLogout, } from "./logout";
|
|
16
|
+
// Provisioning
|
|
17
|
+
export { NoopProvisioningAdapter, createProvisioningAdapter, normalizePayload, SupabaseSyncAdapter, createSupabaseSyncAdapter, } from "./provisioning";
|
|
18
|
+
// Config validation
|
|
19
|
+
export { validateAuthentikConfig, validateSupabaseSyncConfig, validateFullConfig, discoverEndpoints, } from "./config";
|
|
20
|
+
// Safe redirect
|
|
21
|
+
export { resolveSafeRedirect } from "./redirect";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentik logout orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* A correct Authentik logout requires all of these steps in sequence:
|
|
5
|
+
* 1. Clear app-local session state
|
|
6
|
+
* 2. Revoke the access token (best-effort)
|
|
7
|
+
* 3. Build the RP-initiated logout URL with id_token_hint
|
|
8
|
+
* 4. Navigate the browser to the end-session URL
|
|
9
|
+
*
|
|
10
|
+
* Reference: CIG apps/landing/components/AuthProvider.tsx signOut()
|
|
11
|
+
*/
|
|
12
|
+
import type { AuthentikLogoutConfig, AuthentikLogoutResult } from "./types";
|
|
13
|
+
/**
|
|
14
|
+
* Revoke an OAuth2 token at the Authentik revocation endpoint.
|
|
15
|
+
*
|
|
16
|
+
* This is a **best-effort** operation — revocation failures do not
|
|
17
|
+
* prevent logout from completing. Returns `true` if the server
|
|
18
|
+
* responded with 2xx.
|
|
19
|
+
*
|
|
20
|
+
* If `config.revocationEndpoint` is not set, revocation is skipped
|
|
21
|
+
* and the function returns `false`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function revokeToken(config: AuthentikLogoutConfig, accessToken: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Build the Authentik RP-initiated logout URL.
|
|
26
|
+
*
|
|
27
|
+
* The resulting URL, when navigated to, will:
|
|
28
|
+
* 1. Clear the Authentik browser session
|
|
29
|
+
* 2. Redirect back to `postLogoutRedirectUri`
|
|
30
|
+
*
|
|
31
|
+
* Requires that the Authentik provider has an **invalidation flow**
|
|
32
|
+
* configured with a "User Logout" stage and a redirect stage.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildEndSessionUrl(config: AuthentikLogoutConfig, idToken?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Execute the full Authentik logout sequence:
|
|
37
|
+
*
|
|
38
|
+
* 1. Revoke the access token (best-effort)
|
|
39
|
+
* 2. Build the RP-initiated end-session URL
|
|
40
|
+
*
|
|
41
|
+
* The caller is responsible for:
|
|
42
|
+
* - Clearing app-local session state **before** calling this function
|
|
43
|
+
* - Navigating the browser to `result.endSessionUrl` **after** this returns
|
|
44
|
+
*
|
|
45
|
+
* This design keeps the orchestrator framework-agnostic.
|
|
46
|
+
*/
|
|
47
|
+
export declare function orchestrateLogout(config: AuthentikLogoutConfig, tokens: {
|
|
48
|
+
accessToken?: string;
|
|
49
|
+
idToken?: string;
|
|
50
|
+
}): Promise<AuthentikLogoutResult>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentik logout orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* A correct Authentik logout requires all of these steps in sequence:
|
|
5
|
+
* 1. Clear app-local session state
|
|
6
|
+
* 2. Revoke the access token (best-effort)
|
|
7
|
+
* 3. Build the RP-initiated logout URL with id_token_hint
|
|
8
|
+
* 4. Navigate the browser to the end-session URL
|
|
9
|
+
*
|
|
10
|
+
* Reference: CIG apps/landing/components/AuthProvider.tsx signOut()
|
|
11
|
+
*/
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
/* Token revocation */
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
/**
|
|
16
|
+
* Revoke an OAuth2 token at the Authentik revocation endpoint.
|
|
17
|
+
*
|
|
18
|
+
* This is a **best-effort** operation — revocation failures do not
|
|
19
|
+
* prevent logout from completing. Returns `true` if the server
|
|
20
|
+
* responded with 2xx.
|
|
21
|
+
*
|
|
22
|
+
* If `config.revocationEndpoint` is not set, revocation is skipped
|
|
23
|
+
* and the function returns `false`.
|
|
24
|
+
*/
|
|
25
|
+
export async function revokeToken(config, accessToken) {
|
|
26
|
+
if (!config.revocationEndpoint) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const revocationUrl = config.revocationEndpoint;
|
|
30
|
+
const fetchFn = config.fetchFn || fetch;
|
|
31
|
+
const body = new URLSearchParams({
|
|
32
|
+
token: accessToken,
|
|
33
|
+
token_type_hint: "access_token",
|
|
34
|
+
});
|
|
35
|
+
if (config.clientId) {
|
|
36
|
+
body.set("client_id", config.clientId);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetchFn(revocationUrl, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
42
|
+
body,
|
|
43
|
+
});
|
|
44
|
+
return response.ok;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Revocation is best-effort
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/* ------------------------------------------------------------------ */
|
|
52
|
+
/* RP-initiated logout URL */
|
|
53
|
+
/* ------------------------------------------------------------------ */
|
|
54
|
+
/**
|
|
55
|
+
* Build the Authentik RP-initiated logout URL.
|
|
56
|
+
*
|
|
57
|
+
* The resulting URL, when navigated to, will:
|
|
58
|
+
* 1. Clear the Authentik browser session
|
|
59
|
+
* 2. Redirect back to `postLogoutRedirectUri`
|
|
60
|
+
*
|
|
61
|
+
* Requires that the Authentik provider has an **invalidation flow**
|
|
62
|
+
* configured with a "User Logout" stage and a redirect stage.
|
|
63
|
+
*/
|
|
64
|
+
export function buildEndSessionUrl(config, idToken) {
|
|
65
|
+
const endSessionUrl = config.endSessionEndpoint;
|
|
66
|
+
const url = new URL(endSessionUrl);
|
|
67
|
+
if (idToken) {
|
|
68
|
+
url.searchParams.set("id_token_hint", idToken);
|
|
69
|
+
}
|
|
70
|
+
url.searchParams.set("post_logout_redirect_uri", config.postLogoutRedirectUri);
|
|
71
|
+
return url.toString();
|
|
72
|
+
}
|
|
73
|
+
/* ------------------------------------------------------------------ */
|
|
74
|
+
/* Full logout orchestration */
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
/**
|
|
77
|
+
* Execute the full Authentik logout sequence:
|
|
78
|
+
*
|
|
79
|
+
* 1. Revoke the access token (best-effort)
|
|
80
|
+
* 2. Build the RP-initiated end-session URL
|
|
81
|
+
*
|
|
82
|
+
* The caller is responsible for:
|
|
83
|
+
* - Clearing app-local session state **before** calling this function
|
|
84
|
+
* - Navigating the browser to `result.endSessionUrl` **after** this returns
|
|
85
|
+
*
|
|
86
|
+
* This design keeps the orchestrator framework-agnostic.
|
|
87
|
+
*/
|
|
88
|
+
export async function orchestrateLogout(config, tokens) {
|
|
89
|
+
let tokenRevoked = false;
|
|
90
|
+
if (tokens.accessToken) {
|
|
91
|
+
tokenRevoked = await revokeToken(config, tokens.accessToken);
|
|
92
|
+
}
|
|
93
|
+
const endSessionUrl = buildEndSessionUrl(config, tokens.idToken);
|
|
94
|
+
return { endSessionUrl, tokenRevoked };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=logout.js.map
|