@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 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: true // Enable zero-knowledge mode
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 // Enable zero-knowledge mode (default: 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
@@ -1,6 +1,6 @@
1
- export * from "./crypto";
2
- export * from "./dek";
3
- export { setHooks } from "./hooks";
1
+ export * from "./crypto.js";
2
+ export * from "./dek.js";
3
+ export { setHooks } from "./hooks.js";
4
4
  type Config = {
5
5
  issuer: string;
6
6
  clientId: string;
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
- "app-web",
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 keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
97
- "deriveKey",
98
- "deriveBits",
99
- ]);
100
- const publicJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
101
- const privateJwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
102
- sessionStorage.setItem("zk_eph_priv_jwk", JSON.stringify(privateJwk));
103
- const zkPubParam = bytesToBase64Url(new TextEncoder().encode(JSON.stringify(publicJwk)));
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 (cfg.zk !== false)
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 (tokenResponse.zk_drk_hash) {
169
+ if (zkDrkHash) {
149
170
  const hash = bytesToBase64Url(await sha256(new TextEncoder().encode(drkJwe)));
150
- if (tokenResponse.zk_drk_hash !== hash)
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
- return null;
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 || !obfuscatedDrkBase64)
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
- localStorage.removeItem("refresh_token");
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 null;
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.1.1",
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",