@darkauth/client 0.1.1 → 0.2.1
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/dek.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +44 -21
- package/package.json +2 -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/dek.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { compactDecrypt, importJWK } from "jose";
|
|
2
|
-
import { deriveDek, unwrapPrivateKey } from "./crypto";
|
|
3
|
-
import { getHooks } from "./hooks";
|
|
2
|
+
import { deriveDek, unwrapPrivateKey } from "./crypto.js";
|
|
3
|
+
import { getHooks } from "./hooks.js";
|
|
4
4
|
let cachedPrivKey = null;
|
|
5
5
|
let cachedPrivKeyPromise = null;
|
|
6
6
|
async function getUserEncPrivateKey(drk) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { compactDecrypt } from "jose";
|
|
2
|
-
export * from "./crypto";
|
|
3
|
-
export * from "./dek";
|
|
4
|
-
export { setHooks } from "./hooks";
|
|
2
|
+
export * from "./crypto.js";
|
|
3
|
+
export * from "./dek.js";
|
|
4
|
+
export { setHooks } from "./hooks.js";
|
|
5
5
|
function viteEnvGet(key) {
|
|
6
6
|
try {
|
|
7
7
|
const im = import.meta || undefined;
|
|
@@ -19,7 +19,7 @@ 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"
|
|
@@ -28,6 +28,7 @@ let cfg = {
|
|
|
28
28
|
zk: true,
|
|
29
29
|
};
|
|
30
30
|
const OBFUSCATION_KEY = "DarkAuth-Storage-Protection-2025";
|
|
31
|
+
const EMPTY_DRK = new Uint8Array(0);
|
|
31
32
|
export function setConfig(next) {
|
|
32
33
|
cfg = { ...cfg, ...next };
|
|
33
34
|
}
|
|
@@ -93,14 +94,18 @@ export function isTokenValid(token) {
|
|
|
93
94
|
return claims.exp * 1000 > Date.now() + 5000;
|
|
94
95
|
}
|
|
95
96
|
export async function initiateLogin() {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
const zkEnabled = cfg.zk !== false;
|
|
98
|
+
let zkPubParam;
|
|
99
|
+
if (zkEnabled) {
|
|
100
|
+
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
|
|
101
|
+
"deriveKey",
|
|
102
|
+
"deriveBits",
|
|
103
|
+
]);
|
|
104
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
105
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
106
|
+
sessionStorage.setItem("zk_eph_priv_jwk", JSON.stringify(privateJwk));
|
|
107
|
+
zkPubParam = bytesToBase64Url(new TextEncoder().encode(JSON.stringify(publicJwk)));
|
|
108
|
+
}
|
|
104
109
|
const state = crypto.randomUUID();
|
|
105
110
|
const verifier = bytesToBase64Url(crypto.getRandomValues(new Uint8Array(32)));
|
|
106
111
|
sessionStorage.setItem("pkce_verifier", verifier);
|
|
@@ -113,7 +118,7 @@ export async function initiateLogin() {
|
|
|
113
118
|
authUrl.searchParams.set("state", state);
|
|
114
119
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
115
120
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
116
|
-
if (
|
|
121
|
+
if (zkEnabled && zkPubParam)
|
|
117
122
|
authUrl.searchParams.set("zk_pub", zkPubParam);
|
|
118
123
|
location.assign(authUrl.toString());
|
|
119
124
|
}
|
|
@@ -143,22 +148,36 @@ export async function handleCallback() {
|
|
|
143
148
|
const tokenResponse = await response.json();
|
|
144
149
|
const fragmentParams = parseFragmentParams(location.hash || "");
|
|
145
150
|
const drkJwe = fragmentParams.drk_jwe;
|
|
151
|
+
const zkDrkHash = typeof tokenResponse.zk_drk_hash === "string" ? tokenResponse.zk_drk_hash : null;
|
|
152
|
+
const idToken = tokenResponse.id_token;
|
|
153
|
+
const refreshToken = tokenResponse.refresh_token;
|
|
154
|
+
const hasZkArtifacts = !!drkJwe || !!zkDrkHash;
|
|
155
|
+
if (!hasZkArtifacts) {
|
|
156
|
+
sessionStorage.removeItem("zk_eph_priv_jwk");
|
|
157
|
+
try {
|
|
158
|
+
history.replaceState(null, "", location.origin + location.pathname);
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
sessionStorage.setItem("id_token", idToken);
|
|
162
|
+
localStorage.removeItem("drk_protected");
|
|
163
|
+
if (refreshToken)
|
|
164
|
+
localStorage.setItem("refresh_token", refreshToken);
|
|
165
|
+
return { idToken, drk: EMPTY_DRK, refreshToken };
|
|
166
|
+
}
|
|
146
167
|
if (!drkJwe || typeof drkJwe !== "string")
|
|
147
168
|
throw new Error("Missing DRK JWE from URL fragment");
|
|
148
|
-
if (
|
|
169
|
+
if (zkDrkHash) {
|
|
149
170
|
const hash = bytesToBase64Url(await sha256(new TextEncoder().encode(drkJwe)));
|
|
150
|
-
if (
|
|
171
|
+
if (zkDrkHash !== hash)
|
|
151
172
|
throw new Error("DRK hash mismatch");
|
|
152
173
|
}
|
|
153
174
|
const privateJwkString = sessionStorage.getItem("zk_eph_priv_jwk");
|
|
154
175
|
if (!privateJwkString)
|
|
155
|
-
|
|
176
|
+
throw new Error("Missing ZK private key for callback");
|
|
156
177
|
sessionStorage.removeItem("zk_eph_priv_jwk");
|
|
157
178
|
const privateKey = await crypto.subtle.importKey("jwk", JSON.parse(privateJwkString), { name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits", "deriveKey"]);
|
|
158
179
|
const { plaintext } = await compactDecrypt(drkJwe, privateKey);
|
|
159
180
|
const drk = new Uint8Array(plaintext);
|
|
160
|
-
const idToken = tokenResponse.id_token;
|
|
161
|
-
const refreshToken = tokenResponse.refresh_token;
|
|
162
181
|
try {
|
|
163
182
|
history.replaceState(null, "", location.origin + location.pathname);
|
|
164
183
|
}
|
|
@@ -173,10 +192,12 @@ export async function handleCallback() {
|
|
|
173
192
|
export function getStoredSession() {
|
|
174
193
|
const idToken = sessionStorage.getItem("id_token");
|
|
175
194
|
const obfuscatedDrkBase64 = localStorage.getItem("drk_protected");
|
|
176
|
-
if (!idToken
|
|
195
|
+
if (!idToken)
|
|
177
196
|
return null;
|
|
178
197
|
if (!isTokenValid(idToken))
|
|
179
198
|
return null;
|
|
199
|
+
if (!obfuscatedDrkBase64)
|
|
200
|
+
return { idToken, drk: EMPTY_DRK };
|
|
180
201
|
try {
|
|
181
202
|
const obfuscatedDrk = base64UrlToBytes(obfuscatedDrkBase64);
|
|
182
203
|
const drk = deobfuscateKey(obfuscatedDrk);
|
|
@@ -202,7 +223,9 @@ export async function refreshSession() {
|
|
|
202
223
|
}),
|
|
203
224
|
});
|
|
204
225
|
if (!response.ok) {
|
|
205
|
-
|
|
226
|
+
if (response.status === 401) {
|
|
227
|
+
localStorage.removeItem("refresh_token");
|
|
228
|
+
}
|
|
206
229
|
return null;
|
|
207
230
|
}
|
|
208
231
|
const tokenResponse = await response.json();
|
|
@@ -213,7 +236,7 @@ export async function refreshSession() {
|
|
|
213
236
|
localStorage.setItem("refresh_token", newRefreshToken);
|
|
214
237
|
const obfuscatedDrkBase64 = localStorage.getItem("drk_protected");
|
|
215
238
|
if (!obfuscatedDrkBase64)
|
|
216
|
-
return
|
|
239
|
+
return { idToken, drk: EMPTY_DRK, refreshToken: newRefreshToken || refreshToken };
|
|
217
240
|
const obfuscatedDrk = base64UrlToBytes(obfuscatedDrkBase64);
|
|
218
241
|
const drk = deobfuscateKey(obfuscatedDrk);
|
|
219
242
|
return { idToken, drk, refreshToken: newRefreshToken || refreshToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkauth/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
+
"test": "npm run build && node --test --test-reporter=dot --experimental-specifier-resolution=node",
|
|
15
16
|
"prepack": "npm run build",
|
|
16
17
|
"typecheck": "tsc --noEmit",
|
|
17
18
|
"prepare": "tsc",
|