@biowiki/auth-sdk 0.1.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/LICENSE +7 -0
- package/README.md +96 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +305 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @biowiki/auth-sdk
|
|
2
|
+
|
|
3
|
+
Browser OAuth 2.0 Authorization Code + PKCE (`S256`) client for BioWiki Web applications. It keeps only the short-lived access token in `sessionStorage`; the refresh token remains an Identity-managed HttpOnly Cookie.
|
|
4
|
+
It also maintains a stable per-application browser device ID in `localStorage` and sends it when exchanging or refreshing tokens.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
The package is published only to the company private npm Registry. Configure the scope registry in the consuming project's `.npmrc`:
|
|
9
|
+
|
|
10
|
+
```ini
|
|
11
|
+
@biowiki:registry=https://npm.company.example/repository/npm-private/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then install a pinned version:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @biowiki/auth-sdk@0.1.0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { BioWikiAuthClient } from '@biowiki/auth-sdk';
|
|
24
|
+
|
|
25
|
+
export const auth = new BioWikiAuthClient({
|
|
26
|
+
authUrl: import.meta.env.VITE_AUTH_URL,
|
|
27
|
+
clientId: import.meta.env.VITE_SSO_CLIENT_ID,
|
|
28
|
+
redirectUri: `${window.location.origin}/auth/callback`
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export async function login() {
|
|
32
|
+
await auth.login(window.location.pathname + window.location.search);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function completeLogin() {
|
|
36
|
+
const { returnTo } = await auth.completeLogin();
|
|
37
|
+
window.location.replace(returnTo.startsWith('/') ? returnTo : '/');
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
In the callback route, call `completeLogin()` exactly once. For protected pages, use `getSession()` or `requireAuth()`.
|
|
42
|
+
|
|
43
|
+
## Logout
|
|
44
|
+
|
|
45
|
+
First revoke the application's current API session through its same-origin BFF,
|
|
46
|
+
then call `logout()`. The SDK clears its local access token and navigates to
|
|
47
|
+
the Identity Service logout endpoint to clear the SSO session before returning
|
|
48
|
+
to the registered application origin.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
try {
|
|
52
|
+
await fetch('/api/bff/auth/logout', { method: 'POST' });
|
|
53
|
+
} finally {
|
|
54
|
+
await auth.logout();
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Registration
|
|
59
|
+
|
|
60
|
+
The SDK also provides browser registration helpers:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await auth.requestRegistrationCode({
|
|
64
|
+
email: 'user@example.com',
|
|
65
|
+
botToken: '<turnstile-token>'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await auth.register({
|
|
69
|
+
email: 'user@example.com',
|
|
70
|
+
password: 'StrongPassword',
|
|
71
|
+
verificationCode: '381624'
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Publish
|
|
76
|
+
|
|
77
|
+
Package maintainers publish from `packages/auth-sdk`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm ci
|
|
81
|
+
npm run typecheck
|
|
82
|
+
npm run build
|
|
83
|
+
npm pack --dry-run
|
|
84
|
+
npm publish --registry="$NPM_REGISTRY"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Set `NPM_REGISTRY` and `NPM_TOKEN` in CI secrets. Copy `.npmrc.example` to a user-level or CI-generated `.npmrc`, replacing the Registry URL. Never commit an npm token.
|
|
88
|
+
|
|
89
|
+
`prepublishOnly` rebuilds and typechecks the package before publication. Only `dist/`, `README.md`, and `LICENSE` are included in the published tarball.
|
|
90
|
+
|
|
91
|
+
## Security
|
|
92
|
+
|
|
93
|
+
- Do not place `app_secret`, service tokens, JWT signing keys, or npm tokens in frontend environment variables.
|
|
94
|
+
- Register the exact callback URL for each environment in Platform Console.
|
|
95
|
+
- Configure Identity Service `CORS_ORIGIN` to include the application Origin.
|
|
96
|
+
- The SDK does not create Tenants, Applications, OAuth Clients, or API credentials. Use `getSession()`, `isAuthenticated()`, and `requireAuth()` for browser session checks.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
interface AuthSdkConfig {
|
|
2
|
+
authUrl: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
scope?: string;
|
|
6
|
+
}
|
|
7
|
+
interface TokenSet {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
expiresIn: number;
|
|
10
|
+
}
|
|
11
|
+
interface RegistrationInput {
|
|
12
|
+
email: string;
|
|
13
|
+
password: string;
|
|
14
|
+
verificationCode: string;
|
|
15
|
+
}
|
|
16
|
+
interface RegistrationResult<TUser = unknown> {
|
|
17
|
+
user: TUser;
|
|
18
|
+
registered: true;
|
|
19
|
+
}
|
|
20
|
+
interface RegistrationCodeInput {
|
|
21
|
+
email: string;
|
|
22
|
+
botToken?: string;
|
|
23
|
+
}
|
|
24
|
+
declare class BioWikiAuthClient {
|
|
25
|
+
private readonly config;
|
|
26
|
+
constructor(config: AuthSdkConfig);
|
|
27
|
+
private key;
|
|
28
|
+
getToken(): string;
|
|
29
|
+
getDeviceId(): string;
|
|
30
|
+
isAuthenticated(): boolean;
|
|
31
|
+
requestRegistrationCode({ email, botToken }: RegistrationCodeInput): Promise<{
|
|
32
|
+
success: true;
|
|
33
|
+
message: string;
|
|
34
|
+
}>;
|
|
35
|
+
register<TUser = unknown>(input: RegistrationInput): Promise<RegistrationResult<TUser>>;
|
|
36
|
+
login(returnTo?: string): Promise<void>;
|
|
37
|
+
beginRegistration(returnTo?: string): Promise<void>;
|
|
38
|
+
private startAuthorization;
|
|
39
|
+
completeLogin(url?: string): Promise<{
|
|
40
|
+
returnTo: string;
|
|
41
|
+
token: TokenSet;
|
|
42
|
+
}>;
|
|
43
|
+
refresh(): Promise<TokenSet>;
|
|
44
|
+
getUser<T>(): Promise<T>;
|
|
45
|
+
getSession<TUser = unknown>(): Promise<TUser | null>;
|
|
46
|
+
requireAuth(returnTo?: string): Promise<boolean>;
|
|
47
|
+
logout(returnTo?: string): Promise<void>;
|
|
48
|
+
private authRequest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { type AuthSdkConfig, BioWikiAuthClient, type RegistrationCodeInput, type RegistrationInput, type RegistrationResult, type TokenSet };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var storagePrefix = "biowiki.auth-sdk";
|
|
3
|
+
function base64Url(bytes) {
|
|
4
|
+
return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
5
|
+
}
|
|
6
|
+
function randomValue(bytes = 32) {
|
|
7
|
+
const value = new Uint8Array(bytes);
|
|
8
|
+
crypto.getRandomValues(value);
|
|
9
|
+
return base64Url(value);
|
|
10
|
+
}
|
|
11
|
+
async function challenge(verifier) {
|
|
12
|
+
const input = new TextEncoder().encode(verifier);
|
|
13
|
+
if (crypto.subtle) {
|
|
14
|
+
const digest = await crypto.subtle.digest("SHA-256", input);
|
|
15
|
+
return base64Url(new Uint8Array(digest));
|
|
16
|
+
}
|
|
17
|
+
return base64Url(sha256(input));
|
|
18
|
+
}
|
|
19
|
+
function sha256(input) {
|
|
20
|
+
const bitLength = input.length * 8;
|
|
21
|
+
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
|
22
|
+
const bytes = new Uint8Array(paddedLength);
|
|
23
|
+
bytes.set(input);
|
|
24
|
+
bytes[input.length] = 128;
|
|
25
|
+
let length = bitLength;
|
|
26
|
+
for (let index = 0; index < 8; index += 1) {
|
|
27
|
+
bytes[paddedLength - 1 - index] = length & 255;
|
|
28
|
+
length = Math.floor(length / 256);
|
|
29
|
+
}
|
|
30
|
+
const words = new Uint32Array(64);
|
|
31
|
+
const hash = new Uint32Array([
|
|
32
|
+
1779033703,
|
|
33
|
+
3144134277,
|
|
34
|
+
1013904242,
|
|
35
|
+
2773480762,
|
|
36
|
+
1359893119,
|
|
37
|
+
2600822924,
|
|
38
|
+
528734635,
|
|
39
|
+
1541459225
|
|
40
|
+
]);
|
|
41
|
+
for (let offset = 0; offset < bytes.length; offset += 64) {
|
|
42
|
+
for (let index = 0; index < 16; index += 1) {
|
|
43
|
+
const position = offset + index * 4;
|
|
44
|
+
words[index] = bytes[position] << 24 | bytes[position + 1] << 16 | bytes[position + 2] << 8 | bytes[position + 3];
|
|
45
|
+
}
|
|
46
|
+
for (let index = 16; index < 64; index += 1) {
|
|
47
|
+
const a2 = words[index - 15];
|
|
48
|
+
const b2 = words[index - 2];
|
|
49
|
+
const sigma0 = (a2 >>> 7 | a2 << 25) ^ (a2 >>> 18 | a2 << 14) ^ a2 >>> 3;
|
|
50
|
+
const sigma1 = (b2 >>> 17 | b2 << 15) ^ (b2 >>> 19 | b2 << 13) ^ b2 >>> 10;
|
|
51
|
+
words[index] = words[index - 16] + sigma0 + words[index - 7] + sigma1 >>> 0;
|
|
52
|
+
}
|
|
53
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
54
|
+
for (let index = 0; index < 64; index += 1) {
|
|
55
|
+
const sigma1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
|
|
56
|
+
const choice = e & f ^ ~e & g;
|
|
57
|
+
const temp1 = h + sigma1 + choice + SHA256_CONSTANTS[index] + words[index] >>> 0;
|
|
58
|
+
const sigma0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
|
|
59
|
+
const majority = a & b ^ a & c ^ b & c;
|
|
60
|
+
const temp2 = sigma0 + majority >>> 0;
|
|
61
|
+
[h, g, f, e, d, c, b, a] = [g, f, e, d + temp1 >>> 0, c, b, a, temp1 + temp2 >>> 0];
|
|
62
|
+
}
|
|
63
|
+
hash[0] = hash[0] + a >>> 0;
|
|
64
|
+
hash[1] = hash[1] + b >>> 0;
|
|
65
|
+
hash[2] = hash[2] + c >>> 0;
|
|
66
|
+
hash[3] = hash[3] + d >>> 0;
|
|
67
|
+
hash[4] = hash[4] + e >>> 0;
|
|
68
|
+
hash[5] = hash[5] + f >>> 0;
|
|
69
|
+
hash[6] = hash[6] + g >>> 0;
|
|
70
|
+
hash[7] = hash[7] + h >>> 0;
|
|
71
|
+
}
|
|
72
|
+
const digest = new Uint8Array(32);
|
|
73
|
+
for (let index = 0; index < hash.length; index += 1) {
|
|
74
|
+
digest[index * 4] = hash[index] >>> 24;
|
|
75
|
+
digest[index * 4 + 1] = hash[index] >>> 16;
|
|
76
|
+
digest[index * 4 + 2] = hash[index] >>> 8;
|
|
77
|
+
digest[index * 4 + 3] = hash[index];
|
|
78
|
+
}
|
|
79
|
+
return digest;
|
|
80
|
+
}
|
|
81
|
+
var SHA256_CONSTANTS = new Uint32Array([
|
|
82
|
+
1116352408,
|
|
83
|
+
1899447441,
|
|
84
|
+
3049323471,
|
|
85
|
+
3921009573,
|
|
86
|
+
961987163,
|
|
87
|
+
1508970993,
|
|
88
|
+
2453635748,
|
|
89
|
+
2870763221,
|
|
90
|
+
3624381080,
|
|
91
|
+
310598401,
|
|
92
|
+
607225278,
|
|
93
|
+
1426881987,
|
|
94
|
+
1925078388,
|
|
95
|
+
2162078206,
|
|
96
|
+
2614888103,
|
|
97
|
+
3248222580,
|
|
98
|
+
3835390401,
|
|
99
|
+
4022224774,
|
|
100
|
+
264347078,
|
|
101
|
+
604807628,
|
|
102
|
+
770255983,
|
|
103
|
+
1249150122,
|
|
104
|
+
1555081692,
|
|
105
|
+
1996064986,
|
|
106
|
+
2554220882,
|
|
107
|
+
2821834349,
|
|
108
|
+
2952996808,
|
|
109
|
+
3210313671,
|
|
110
|
+
3336571891,
|
|
111
|
+
3584528711,
|
|
112
|
+
113926993,
|
|
113
|
+
338241895,
|
|
114
|
+
666307205,
|
|
115
|
+
773529912,
|
|
116
|
+
1294757372,
|
|
117
|
+
1396182291,
|
|
118
|
+
1695183700,
|
|
119
|
+
1986661051,
|
|
120
|
+
2177026350,
|
|
121
|
+
2456956037,
|
|
122
|
+
2730485921,
|
|
123
|
+
2820302411,
|
|
124
|
+
3259730800,
|
|
125
|
+
3345764771,
|
|
126
|
+
3516065817,
|
|
127
|
+
3600352804,
|
|
128
|
+
4094571909,
|
|
129
|
+
275423344,
|
|
130
|
+
430227734,
|
|
131
|
+
506948616,
|
|
132
|
+
659060556,
|
|
133
|
+
883997877,
|
|
134
|
+
958139571,
|
|
135
|
+
1322822218,
|
|
136
|
+
1537002063,
|
|
137
|
+
1747873779,
|
|
138
|
+
1955562222,
|
|
139
|
+
2024104815,
|
|
140
|
+
2227730452,
|
|
141
|
+
2361852424,
|
|
142
|
+
2428436474,
|
|
143
|
+
2756734187,
|
|
144
|
+
3204031479,
|
|
145
|
+
3329325298
|
|
146
|
+
]);
|
|
147
|
+
var BioWikiAuthClient = class {
|
|
148
|
+
constructor(config) {
|
|
149
|
+
this.config = config;
|
|
150
|
+
}
|
|
151
|
+
config;
|
|
152
|
+
key(name) {
|
|
153
|
+
return `${storagePrefix}:${this.config.clientId}:${name}`;
|
|
154
|
+
}
|
|
155
|
+
getToken() {
|
|
156
|
+
return sessionStorage.getItem(this.key("access-token")) ?? "";
|
|
157
|
+
}
|
|
158
|
+
getDeviceId() {
|
|
159
|
+
const key = this.key("device-id");
|
|
160
|
+
const existing = localStorage.getItem(key);
|
|
161
|
+
if (existing) return existing;
|
|
162
|
+
const deviceId = `web-${randomValue(24)}`;
|
|
163
|
+
localStorage.setItem(key, deviceId);
|
|
164
|
+
return deviceId;
|
|
165
|
+
}
|
|
166
|
+
isAuthenticated() {
|
|
167
|
+
return Boolean(this.getToken());
|
|
168
|
+
}
|
|
169
|
+
async requestRegistrationCode({ email, botToken }) {
|
|
170
|
+
return this.authRequest("/api/v1/auth/email/send-code", {
|
|
171
|
+
email,
|
|
172
|
+
purpose: "register",
|
|
173
|
+
...botToken ? { bot_token: botToken } : {}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async register(input) {
|
|
177
|
+
return this.authRequest("/api/v1/auth/register", {
|
|
178
|
+
email: input.email,
|
|
179
|
+
password: input.password,
|
|
180
|
+
verification_code: input.verificationCode
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async login(returnTo = "/") {
|
|
184
|
+
await this.startAuthorization(returnTo);
|
|
185
|
+
}
|
|
186
|
+
async beginRegistration(returnTo = "/") {
|
|
187
|
+
await this.startAuthorization(returnTo, "signup");
|
|
188
|
+
}
|
|
189
|
+
async startAuthorization(returnTo, screenHint) {
|
|
190
|
+
const state = randomValue();
|
|
191
|
+
const verifier = randomValue(48);
|
|
192
|
+
sessionStorage.setItem(this.key("state"), state);
|
|
193
|
+
sessionStorage.setItem(this.key("verifier"), verifier);
|
|
194
|
+
sessionStorage.setItem(this.key("return-to"), returnTo);
|
|
195
|
+
const authorize = new URL("/oauth/authorize", this.config.authUrl);
|
|
196
|
+
authorize.search = new URLSearchParams({
|
|
197
|
+
response_type: "code",
|
|
198
|
+
client_id: this.config.clientId,
|
|
199
|
+
redirect_uri: this.config.redirectUri,
|
|
200
|
+
scope: this.config.scope ?? "openid profile email",
|
|
201
|
+
state,
|
|
202
|
+
code_challenge: await challenge(verifier),
|
|
203
|
+
code_challenge_method: "S256"
|
|
204
|
+
}).toString();
|
|
205
|
+
if (screenHint) authorize.searchParams.set("screen_hint", screenHint);
|
|
206
|
+
window.location.assign(authorize.toString());
|
|
207
|
+
}
|
|
208
|
+
async completeLogin(url = window.location.href) {
|
|
209
|
+
const callback = new URL(url);
|
|
210
|
+
const code = callback.searchParams.get("code");
|
|
211
|
+
const state = callback.searchParams.get("state");
|
|
212
|
+
const expectedState = sessionStorage.getItem(this.key("state"));
|
|
213
|
+
const verifier = sessionStorage.getItem(this.key("verifier"));
|
|
214
|
+
if (!code || !state || state !== expectedState || !verifier) {
|
|
215
|
+
throw new Error("Invalid OAuth callback state.");
|
|
216
|
+
}
|
|
217
|
+
const response = await fetch(new URL("/oauth/token", this.config.authUrl), {
|
|
218
|
+
method: "POST",
|
|
219
|
+
credentials: "include",
|
|
220
|
+
headers: {
|
|
221
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
222
|
+
"x-device-id": this.getDeviceId()
|
|
223
|
+
},
|
|
224
|
+
body: new URLSearchParams({
|
|
225
|
+
grant_type: "authorization_code",
|
|
226
|
+
code,
|
|
227
|
+
client_id: this.config.clientId,
|
|
228
|
+
redirect_uri: this.config.redirectUri,
|
|
229
|
+
code_verifier: verifier
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) throw new Error("OAuth authorization code exchange failed.");
|
|
233
|
+
const payload = await response.json();
|
|
234
|
+
const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };
|
|
235
|
+
sessionStorage.setItem(this.key("access-token"), token.accessToken);
|
|
236
|
+
const returnTo = sessionStorage.getItem(this.key("return-to")) ?? "/";
|
|
237
|
+
sessionStorage.removeItem(this.key("state"));
|
|
238
|
+
sessionStorage.removeItem(this.key("verifier"));
|
|
239
|
+
sessionStorage.removeItem(this.key("return-to"));
|
|
240
|
+
return { returnTo, token };
|
|
241
|
+
}
|
|
242
|
+
async refresh() {
|
|
243
|
+
const response = await fetch(new URL("/oauth/token", this.config.authUrl), {
|
|
244
|
+
method: "POST",
|
|
245
|
+
credentials: "include",
|
|
246
|
+
headers: {
|
|
247
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
248
|
+
"x-device-id": this.getDeviceId()
|
|
249
|
+
},
|
|
250
|
+
body: new URLSearchParams({ grant_type: "refresh_token", client_id: this.config.clientId })
|
|
251
|
+
});
|
|
252
|
+
if (!response.ok) throw new Error("OAuth refresh failed.");
|
|
253
|
+
const payload = await response.json();
|
|
254
|
+
const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };
|
|
255
|
+
sessionStorage.setItem(this.key("access-token"), token.accessToken);
|
|
256
|
+
return token;
|
|
257
|
+
}
|
|
258
|
+
async getUser() {
|
|
259
|
+
const token = this.getToken() || (await this.refresh()).accessToken;
|
|
260
|
+
const response = await fetch(new URL("/oauth/userinfo", this.config.authUrl), {
|
|
261
|
+
headers: { authorization: `Bearer ${token}` },
|
|
262
|
+
credentials: "include"
|
|
263
|
+
});
|
|
264
|
+
if (!response.ok) throw new Error("OAuth userinfo request failed.");
|
|
265
|
+
return response.json();
|
|
266
|
+
}
|
|
267
|
+
async getSession() {
|
|
268
|
+
try {
|
|
269
|
+
return await this.getUser();
|
|
270
|
+
} catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async requireAuth(returnTo = window.location.pathname + window.location.search) {
|
|
275
|
+
if (this.isAuthenticated()) return true;
|
|
276
|
+
await this.login(returnTo);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
async logout(returnTo = `${window.location.origin}/`) {
|
|
280
|
+
const logout = new URL("/oauth/logout", this.config.authUrl);
|
|
281
|
+
logout.search = new URLSearchParams({
|
|
282
|
+
client_id: this.config.clientId,
|
|
283
|
+
post_logout_redirect_uri: returnTo
|
|
284
|
+
}).toString();
|
|
285
|
+
sessionStorage.removeItem(this.key("access-token"));
|
|
286
|
+
window.location.assign(logout.toString());
|
|
287
|
+
}
|
|
288
|
+
async authRequest(path, body) {
|
|
289
|
+
const response = await fetch(new URL(path, this.config.authUrl), {
|
|
290
|
+
method: "POST",
|
|
291
|
+
credentials: "include",
|
|
292
|
+
headers: { "content-type": "application/json" },
|
|
293
|
+
body: JSON.stringify(body)
|
|
294
|
+
});
|
|
295
|
+
const payload = await response.json().catch(() => null);
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
throw new Error(payload?.error?.message ?? "Identity registration request failed.");
|
|
298
|
+
}
|
|
299
|
+
return payload?.data ?? payload;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
export {
|
|
303
|
+
BioWikiAuthClient
|
|
304
|
+
};
|
|
305
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface AuthSdkConfig {\n authUrl: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n}\n\nexport interface TokenSet {\n accessToken: string;\n expiresIn: number;\n}\n\nexport interface RegistrationInput {\n email: string;\n password: string;\n verificationCode: string;\n}\n\nexport interface RegistrationResult<TUser = unknown> {\n user: TUser;\n registered: true;\n}\n\nexport interface RegistrationCodeInput {\n email: string;\n botToken?: string;\n}\n\nconst storagePrefix = 'biowiki.auth-sdk';\n\nfunction base64Url(bytes: Uint8Array) {\n return btoa(String.fromCharCode(...bytes))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replaceAll('=', '');\n}\n\nfunction randomValue(bytes = 32) {\n const value = new Uint8Array(bytes);\n crypto.getRandomValues(value);\n return base64Url(value);\n}\n\nasync function challenge(verifier: string) {\n const input = new TextEncoder().encode(verifier);\n if (crypto.subtle) {\n const digest = await crypto.subtle.digest('SHA-256', input);\n return base64Url(new Uint8Array(digest));\n }\n // Browsers do not expose SubtleCrypto for HTTP pages served from a LAN IP.\n return base64Url(sha256(input));\n}\n\nfunction sha256(input: Uint8Array) {\n const bitLength = input.length * 8;\n const paddedLength = Math.ceil((input.length + 9) / 64) * 64;\n const bytes = new Uint8Array(paddedLength);\n bytes.set(input);\n bytes[input.length] = 0x80;\n\n let length = bitLength;\n for (let index = 0; index < 8; index += 1) {\n bytes[paddedLength - 1 - index] = length & 0xff;\n length = Math.floor(length / 256);\n }\n\n const words = new Uint32Array(64);\n const hash = new Uint32Array([\n 0x6a09e667,\n 0xbb67ae85,\n 0x3c6ef372,\n 0xa54ff53a,\n 0x510e527f,\n 0x9b05688c,\n 0x1f83d9ab,\n 0x5be0cd19\n ]);\n\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let index = 0; index < 16; index += 1) {\n const position = offset + index * 4;\n words[index] =\n (bytes[position] << 24) |\n (bytes[position + 1] << 16) |\n (bytes[position + 2] << 8) |\n bytes[position + 3];\n }\n for (let index = 16; index < 64; index += 1) {\n const a = words[index - 15];\n const b = words[index - 2];\n const sigma0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);\n const sigma1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);\n words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;\n }\n\n let [a, b, c, d, e, f, g, h] = hash;\n for (let index = 0; index < 64; index += 1) {\n const sigma1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));\n const choice = (e & f) ^ (~e & g);\n const temp1 = (h + sigma1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0;\n const sigma0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const temp2 = (sigma0 + majority) >>> 0;\n [h, g, f, e, d, c, b, a] = [g, f, e, (d + temp1) >>> 0, c, b, a, (temp1 + temp2) >>> 0];\n }\n\n hash[0] = (hash[0] + a) >>> 0;\n hash[1] = (hash[1] + b) >>> 0;\n hash[2] = (hash[2] + c) >>> 0;\n hash[3] = (hash[3] + d) >>> 0;\n hash[4] = (hash[4] + e) >>> 0;\n hash[5] = (hash[5] + f) >>> 0;\n hash[6] = (hash[6] + g) >>> 0;\n hash[7] = (hash[7] + h) >>> 0;\n }\n\n const digest = new Uint8Array(32);\n for (let index = 0; index < hash.length; index += 1) {\n digest[index * 4] = hash[index] >>> 24;\n digest[index * 4 + 1] = hash[index] >>> 16;\n digest[index * 4 + 2] = hash[index] >>> 8;\n digest[index * 4 + 3] = hash[index];\n }\n return digest;\n}\n\nconst SHA256_CONSTANTS = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\nexport class BioWikiAuthClient {\n constructor(private readonly config: AuthSdkConfig) {}\n\n private key(name: string) {\n return `${storagePrefix}:${this.config.clientId}:${name}`;\n }\n\n getToken() {\n return sessionStorage.getItem(this.key('access-token')) ?? '';\n }\n\n getDeviceId() {\n const key = this.key('device-id');\n const existing = localStorage.getItem(key);\n if (existing) return existing;\n const deviceId = `web-${randomValue(24)}`;\n localStorage.setItem(key, deviceId);\n return deviceId;\n }\n\n isAuthenticated() {\n return Boolean(this.getToken());\n }\n\n async requestRegistrationCode({ email, botToken }: RegistrationCodeInput) {\n return this.authRequest('/api/v1/auth/email/send-code', {\n email,\n purpose: 'register',\n ...(botToken ? { bot_token: botToken } : {})\n });\n }\n\n async register<TUser = unknown>(input: RegistrationInput): Promise<RegistrationResult<TUser>> {\n return this.authRequest<RegistrationResult<TUser>>('/api/v1/auth/register', {\n email: input.email,\n password: input.password,\n verification_code: input.verificationCode\n });\n }\n\n async login(returnTo = '/') {\n await this.startAuthorization(returnTo);\n }\n\n async beginRegistration(returnTo = '/') {\n await this.startAuthorization(returnTo, 'signup');\n }\n\n private async startAuthorization(returnTo: string, screenHint?: 'signup') {\n const state = randomValue();\n const verifier = randomValue(48);\n sessionStorage.setItem(this.key('state'), state);\n sessionStorage.setItem(this.key('verifier'), verifier);\n sessionStorage.setItem(this.key('return-to'), returnTo);\n const authorize = new URL('/oauth/authorize', this.config.authUrl);\n authorize.search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n scope: this.config.scope ?? 'openid profile email',\n state,\n code_challenge: await challenge(verifier),\n code_challenge_method: 'S256'\n }).toString();\n if (screenHint) authorize.searchParams.set('screen_hint', screenHint);\n window.location.assign(authorize.toString());\n }\n\n async completeLogin(url = window.location.href): Promise<{ returnTo: string; token: TokenSet }> {\n const callback = new URL(url);\n const code = callback.searchParams.get('code');\n const state = callback.searchParams.get('state');\n const expectedState = sessionStorage.getItem(this.key('state'));\n const verifier = sessionStorage.getItem(this.key('verifier'));\n if (!code || !state || state !== expectedState || !verifier) {\n throw new Error('Invalid OAuth callback state.');\n }\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n code_verifier: verifier\n })\n });\n if (!response.ok) throw new Error('OAuth authorization code exchange failed.');\n const payload = await response.json();\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n const returnTo = sessionStorage.getItem(this.key('return-to')) ?? '/';\n sessionStorage.removeItem(this.key('state'));\n sessionStorage.removeItem(this.key('verifier'));\n sessionStorage.removeItem(this.key('return-to'));\n return { returnTo, token };\n }\n\n async refresh(): Promise<TokenSet> {\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({ grant_type: 'refresh_token', client_id: this.config.clientId })\n });\n if (!response.ok) throw new Error('OAuth refresh failed.');\n const payload = await response.json();\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n return token;\n }\n\n async getUser<T>() {\n const token = this.getToken() || (await this.refresh()).accessToken;\n const response = await fetch(new URL('/oauth/userinfo', this.config.authUrl), {\n headers: { authorization: `Bearer ${token}` },\n credentials: 'include'\n });\n if (!response.ok) throw new Error('OAuth userinfo request failed.');\n return response.json() as Promise<T>;\n }\n\n async getSession<TUser = unknown>(): Promise<TUser | null> {\n try {\n return await this.getUser<TUser>();\n } catch {\n return null;\n }\n }\n\n async requireAuth(returnTo = window.location.pathname + window.location.search) {\n if (this.isAuthenticated()) return true;\n await this.login(returnTo);\n return false;\n }\n\n async logout(returnTo = `${window.location.origin}/`) {\n const logout = new URL('/oauth/logout', this.config.authUrl);\n logout.search = new URLSearchParams({\n client_id: this.config.clientId,\n post_logout_redirect_uri: returnTo\n }).toString();\n sessionStorage.removeItem(this.key('access-token'));\n window.location.assign(logout.toString());\n }\n\n private async authRequest<T = { success: true; message: string }>(path: string, body: Record<string, string>) {\n const response = await fetch(new URL(path, this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body)\n });\n const payload = await response.json().catch(() => null);\n if (!response.ok) {\n throw new Error(payload?.error?.message ?? 'Identity registration request failed.');\n }\n return (payload?.data ?? payload) as T;\n }\n}\n"],"mappings":"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","names":["a","b"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@biowiki/auth-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser OAuth 2.0 Authorization Code + PKCE client for BioWiki applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"prepack": "npm run typecheck && npm run build",
|
|
23
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "restricted"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24 <25"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.5.1",
|
|
33
|
+
"typescript": "^5.7.3"
|
|
34
|
+
}
|
|
35
|
+
}
|