@darkauth/client 0.2.0 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/index.js +42 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript client library for DarkAuth - providing zero-knowledge authentication and client-side encryption capabilities for web applications.
|
|
4
4
|
|
|
5
|
+
The client supports both:
|
|
6
|
+
- ZK-enabled OAuth/OIDC flows
|
|
7
|
+
- Standard OAuth/OIDC flows without ZK delivery
|
|
8
|
+
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- **Zero-Knowledge Authentication**: Secure OAuth2/OIDC flow with PKCE and ephemeral key exchange
|
|
@@ -29,7 +33,7 @@ setConfig({
|
|
|
29
33
|
issuer: 'https://auth.example.com',
|
|
30
34
|
clientId: 'your-client-id',
|
|
31
35
|
redirectUri: 'https://app.example.com/callback',
|
|
32
|
-
zk:
|
|
36
|
+
zk: false // Optional: disable ZK request parameters for standard OIDC flows
|
|
33
37
|
});
|
|
34
38
|
|
|
35
39
|
// Start login flow
|
|
@@ -61,7 +65,7 @@ setConfig({
|
|
|
61
65
|
issuer: 'https://auth.example.com', // DarkAuth server URL
|
|
62
66
|
clientId: 'your-client-id', // Your application's client ID
|
|
63
67
|
redirectUri: 'https://app.example.com/callback', // OAuth callback URL
|
|
64
|
-
zk: true //
|
|
68
|
+
zk: true // Optional. Default true. Set false for non-ZK flows.
|
|
65
69
|
});
|
|
66
70
|
```
|
|
67
71
|
|
|
@@ -80,20 +84,24 @@ Starts the OAuth2/OIDC login flow with PKCE. Redirects the user to the DarkAuth
|
|
|
80
84
|
|
|
81
85
|
Processes the OAuth callback after successful authentication. Returns an `AuthSession` object containing:
|
|
82
86
|
- `idToken`: JWT ID token
|
|
83
|
-
- `drk`: Derived Root Key for encryption operations
|
|
87
|
+
- `drk`: Derived Root Key for encryption operations. In non-ZK flows this is an empty `Uint8Array`.
|
|
84
88
|
- `refreshToken?`: Optional refresh token
|
|
85
89
|
|
|
90
|
+
Behavior:
|
|
91
|
+
- If ZK artifacts are present in the callback/token response, ZK validation and DRK decryption are enforced.
|
|
92
|
+
- If no ZK artifacts are present, callback still succeeds as a standard OIDC flow.
|
|
93
|
+
|
|
86
94
|
#### `logout(): void`
|
|
87
95
|
|
|
88
96
|
Clears all authentication data from storage.
|
|
89
97
|
|
|
90
98
|
#### `getStoredSession(): AuthSession | null`
|
|
91
99
|
|
|
92
|
-
Retrieves the current session from storage if valid.
|
|
100
|
+
Retrieves the current session from storage if valid. For non-ZK sessions, returns `drk` as an empty `Uint8Array`.
|
|
93
101
|
|
|
94
102
|
#### `refreshSession(): Promise<AuthSession | null>`
|
|
95
103
|
|
|
96
|
-
Refreshes the current session using the stored refresh token.
|
|
104
|
+
Refreshes the current session using the stored refresh token. For non-ZK sessions, returns `drk` as an empty `Uint8Array`.
|
|
97
105
|
|
|
98
106
|
### User Information
|
|
99
107
|
|
package/dist/index.js
CHANGED
|
@@ -19,17 +19,29 @@ let cfg = {
|
|
|
19
19
|
clientId: (typeof window !== "undefined" && window.__APP_CONFIG__?.clientId) ||
|
|
20
20
|
viteEnvGet("VITE_CLIENT_ID") ||
|
|
21
21
|
(typeof process !== "undefined" ? process.env.DARKAUTH_CLIENT_ID : undefined) ||
|
|
22
|
-
"
|
|
22
|
+
"demo-public-client",
|
|
23
23
|
redirectUri: (typeof window !== "undefined" && window.__APP_CONFIG__?.redirectUri) ||
|
|
24
24
|
viteEnvGet("VITE_REDIRECT_URI") ||
|
|
25
25
|
(typeof window !== "undefined"
|
|
26
26
|
? `${window.location.origin}/callback`
|
|
27
27
|
: "http://localhost:5173/callback"),
|
|
28
|
+
zk: true,
|
|
28
29
|
};
|
|
29
30
|
const OBFUSCATION_KEY = "DarkAuth-Storage-Protection-2025";
|
|
31
|
+
const EMPTY_DRK = new Uint8Array(0);
|
|
32
|
+
const ID_TOKEN_KEY = "id_token";
|
|
30
33
|
export function setConfig(next) {
|
|
31
34
|
cfg = { ...cfg, ...next };
|
|
32
35
|
}
|
|
36
|
+
function setStoredIdToken(token) {
|
|
37
|
+
localStorage.setItem(ID_TOKEN_KEY, token);
|
|
38
|
+
}
|
|
39
|
+
function getStoredIdToken() {
|
|
40
|
+
return localStorage.getItem(ID_TOKEN_KEY);
|
|
41
|
+
}
|
|
42
|
+
function clearStoredIdToken() {
|
|
43
|
+
localStorage.removeItem(ID_TOKEN_KEY);
|
|
44
|
+
}
|
|
33
45
|
function bytesToBase64Url(bytes) {
|
|
34
46
|
let s = "";
|
|
35
47
|
for (const b of bytes)
|
|
@@ -92,7 +104,7 @@ export function isTokenValid(token) {
|
|
|
92
104
|
return claims.exp * 1000 > Date.now() + 5000;
|
|
93
105
|
}
|
|
94
106
|
export async function initiateLogin() {
|
|
95
|
-
const zkEnabled = cfg.zk
|
|
107
|
+
const zkEnabled = cfg.zk !== false;
|
|
96
108
|
let zkPubParam;
|
|
97
109
|
if (zkEnabled) {
|
|
98
110
|
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
|
|
@@ -146,27 +158,41 @@ export async function handleCallback() {
|
|
|
146
158
|
const tokenResponse = await response.json();
|
|
147
159
|
const fragmentParams = parseFragmentParams(location.hash || "");
|
|
148
160
|
const drkJwe = fragmentParams.drk_jwe;
|
|
161
|
+
const zkDrkHash = typeof tokenResponse.zk_drk_hash === "string" ? tokenResponse.zk_drk_hash : null;
|
|
162
|
+
const idToken = tokenResponse.id_token;
|
|
163
|
+
const refreshToken = tokenResponse.refresh_token;
|
|
164
|
+
const hasZkArtifacts = !!drkJwe || !!zkDrkHash;
|
|
165
|
+
if (!hasZkArtifacts) {
|
|
166
|
+
sessionStorage.removeItem("zk_eph_priv_jwk");
|
|
167
|
+
try {
|
|
168
|
+
history.replaceState(null, "", location.origin + location.pathname);
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
setStoredIdToken(idToken);
|
|
172
|
+
localStorage.removeItem("drk_protected");
|
|
173
|
+
if (refreshToken)
|
|
174
|
+
localStorage.setItem("refresh_token", refreshToken);
|
|
175
|
+
return { idToken, drk: EMPTY_DRK, refreshToken };
|
|
176
|
+
}
|
|
149
177
|
if (!drkJwe || typeof drkJwe !== "string")
|
|
150
178
|
throw new Error("Missing DRK JWE from URL fragment");
|
|
151
|
-
if (
|
|
179
|
+
if (zkDrkHash) {
|
|
152
180
|
const hash = bytesToBase64Url(await sha256(new TextEncoder().encode(drkJwe)));
|
|
153
|
-
if (
|
|
181
|
+
if (zkDrkHash !== hash)
|
|
154
182
|
throw new Error("DRK hash mismatch");
|
|
155
183
|
}
|
|
156
184
|
const privateJwkString = sessionStorage.getItem("zk_eph_priv_jwk");
|
|
157
185
|
if (!privateJwkString)
|
|
158
|
-
|
|
186
|
+
throw new Error("Missing ZK private key for callback");
|
|
159
187
|
sessionStorage.removeItem("zk_eph_priv_jwk");
|
|
160
188
|
const privateKey = await crypto.subtle.importKey("jwk", JSON.parse(privateJwkString), { name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits", "deriveKey"]);
|
|
161
189
|
const { plaintext } = await compactDecrypt(drkJwe, privateKey);
|
|
162
190
|
const drk = new Uint8Array(plaintext);
|
|
163
|
-
const idToken = tokenResponse.id_token;
|
|
164
|
-
const refreshToken = tokenResponse.refresh_token;
|
|
165
191
|
try {
|
|
166
192
|
history.replaceState(null, "", location.origin + location.pathname);
|
|
167
193
|
}
|
|
168
194
|
catch { }
|
|
169
|
-
|
|
195
|
+
setStoredIdToken(idToken);
|
|
170
196
|
const obfuscatedDrk = obfuscateKey(drk);
|
|
171
197
|
localStorage.setItem("drk_protected", bytesToBase64Url(obfuscatedDrk));
|
|
172
198
|
if (refreshToken)
|
|
@@ -174,12 +200,14 @@ export async function handleCallback() {
|
|
|
174
200
|
return { idToken, drk, refreshToken };
|
|
175
201
|
}
|
|
176
202
|
export function getStoredSession() {
|
|
177
|
-
const idToken =
|
|
203
|
+
const idToken = getStoredIdToken();
|
|
178
204
|
const obfuscatedDrkBase64 = localStorage.getItem("drk_protected");
|
|
179
|
-
if (!idToken
|
|
205
|
+
if (!idToken)
|
|
180
206
|
return null;
|
|
181
207
|
if (!isTokenValid(idToken))
|
|
182
208
|
return null;
|
|
209
|
+
if (!obfuscatedDrkBase64)
|
|
210
|
+
return { idToken, drk: EMPTY_DRK };
|
|
183
211
|
try {
|
|
184
212
|
const obfuscatedDrk = base64UrlToBytes(obfuscatedDrkBase64);
|
|
185
213
|
const drk = deobfuscateKey(obfuscatedDrk);
|
|
@@ -213,25 +241,25 @@ export async function refreshSession() {
|
|
|
213
241
|
const tokenResponse = await response.json();
|
|
214
242
|
const idToken = tokenResponse.id_token;
|
|
215
243
|
const newRefreshToken = tokenResponse.refresh_token;
|
|
216
|
-
|
|
244
|
+
setStoredIdToken(idToken);
|
|
217
245
|
if (newRefreshToken)
|
|
218
246
|
localStorage.setItem("refresh_token", newRefreshToken);
|
|
219
247
|
const obfuscatedDrkBase64 = localStorage.getItem("drk_protected");
|
|
220
248
|
if (!obfuscatedDrkBase64)
|
|
221
|
-
return
|
|
249
|
+
return { idToken, drk: EMPTY_DRK, refreshToken: newRefreshToken || refreshToken };
|
|
222
250
|
const obfuscatedDrk = base64UrlToBytes(obfuscatedDrkBase64);
|
|
223
251
|
const drk = deobfuscateKey(obfuscatedDrk);
|
|
224
252
|
return { idToken, drk, refreshToken: newRefreshToken || refreshToken };
|
|
225
253
|
}
|
|
226
254
|
export function logout() {
|
|
227
|
-
|
|
255
|
+
clearStoredIdToken();
|
|
228
256
|
localStorage.removeItem("drk_protected");
|
|
229
257
|
sessionStorage.removeItem("zk_eph_priv_jwk");
|
|
230
258
|
sessionStorage.removeItem("pkce_verifier");
|
|
231
259
|
localStorage.removeItem("refresh_token");
|
|
232
260
|
}
|
|
233
261
|
export function getCurrentUser() {
|
|
234
|
-
const idToken =
|
|
262
|
+
const idToken = getStoredIdToken();
|
|
235
263
|
if (!idToken)
|
|
236
264
|
return null;
|
|
237
265
|
return parseJwt(idToken);
|