@ave-id/sdk 0.2.0 → 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/dist/client.d.ts +6 -0
- package/dist/client.js +19 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +210 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +39 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { buildAuthorizeUrl, generateCodeChallenge, generateCodeVerifier, generateNonce } from "./index";
|
|
2
|
+
export async function startPkceLogin(params) {
|
|
3
|
+
const verifier = generateCodeVerifier();
|
|
4
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
5
|
+
const nonce = generateNonce();
|
|
6
|
+
sessionStorage.setItem("ave_code_verifier", verifier);
|
|
7
|
+
sessionStorage.setItem("ave_nonce", nonce);
|
|
8
|
+
const url = buildAuthorizeUrl({
|
|
9
|
+
clientId: params.clientId,
|
|
10
|
+
redirectUri: params.redirectUri,
|
|
11
|
+
issuer: params.issuer,
|
|
12
|
+
}, {
|
|
13
|
+
scope: (params.scope || "openid profile email").split(" "),
|
|
14
|
+
nonce,
|
|
15
|
+
codeChallenge: challenge,
|
|
16
|
+
codeChallengeMethod: "S256",
|
|
17
|
+
});
|
|
18
|
+
window.location.href = url;
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type Scope = "openid" | "profile" | "email" | "offline_access" | "user_id";
|
|
2
|
+
export interface AveConfig {
|
|
3
|
+
clientId: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
issuer?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function generateCodeVerifier(): string;
|
|
8
|
+
export declare function generateCodeChallenge(verifier: string): Promise<string>;
|
|
9
|
+
export declare function generateNonce(): string;
|
|
10
|
+
export declare function buildAuthorizeUrl(config: AveConfig, params?: {
|
|
11
|
+
scope?: Scope[];
|
|
12
|
+
state?: string;
|
|
13
|
+
nonce?: string;
|
|
14
|
+
codeChallenge?: string;
|
|
15
|
+
codeChallengeMethod?: "S256" | "plain";
|
|
16
|
+
extraParams?: Record<string, string>;
|
|
17
|
+
}): string;
|
|
18
|
+
export declare function exchangeCode(config: AveConfig, payload: {
|
|
19
|
+
code: string;
|
|
20
|
+
codeVerifier?: string;
|
|
21
|
+
}): Promise<import("./types").TokenResponse>;
|
|
22
|
+
export declare function refreshToken(config: AveConfig, payload: {
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
}): Promise<import("./types").TokenResponse>;
|
|
25
|
+
export declare function fetchUserInfo(config: AveConfig, accessToken: string): Promise<import("./types").UserInfo>;
|
|
26
|
+
export declare function getApiBase(issuer?: string): string;
|
|
27
|
+
export interface SigningConfig {
|
|
28
|
+
clientId: string;
|
|
29
|
+
clientSecret: string;
|
|
30
|
+
issuer?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a signature request for a user identity
|
|
34
|
+
* This is called from your server with client credentials
|
|
35
|
+
*/
|
|
36
|
+
export declare function createSignatureRequest(config: SigningConfig, params: {
|
|
37
|
+
identityId: string;
|
|
38
|
+
payload: string;
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
expiresInSeconds?: number;
|
|
41
|
+
}): Promise<import("./types").SignatureRequest>;
|
|
42
|
+
/**
|
|
43
|
+
* Check the status of a signature request
|
|
44
|
+
*/
|
|
45
|
+
export declare function getSignatureStatus(config: {
|
|
46
|
+
clientId: string;
|
|
47
|
+
issuer?: string;
|
|
48
|
+
}, requestId: string): Promise<import("./types").SignatureResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the public signing key for an identity by handle
|
|
51
|
+
*/
|
|
52
|
+
export declare function getPublicKey(config: {
|
|
53
|
+
issuer?: string;
|
|
54
|
+
}, handle: string): Promise<{
|
|
55
|
+
handle: string;
|
|
56
|
+
publicKey: string;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Verify a signature using the Ave API
|
|
61
|
+
*/
|
|
62
|
+
export declare function verifySignature(config: {
|
|
63
|
+
issuer?: string;
|
|
64
|
+
}, params: {
|
|
65
|
+
message: string;
|
|
66
|
+
signature: string;
|
|
67
|
+
publicKey: string;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
valid: boolean;
|
|
70
|
+
error?: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Build the URL to redirect a user for signing
|
|
74
|
+
* Use this for browser-based signing flows
|
|
75
|
+
*/
|
|
76
|
+
export declare function buildSigningUrl(config: {
|
|
77
|
+
issuer?: string;
|
|
78
|
+
}, requestId: string, options?: {
|
|
79
|
+
embed?: boolean;
|
|
80
|
+
}): string;
|
|
81
|
+
/**
|
|
82
|
+
* Open a popup window for signing
|
|
83
|
+
* Returns a promise that resolves when the user signs or denies
|
|
84
|
+
*/
|
|
85
|
+
export declare function openSigningPopup(config: {
|
|
86
|
+
issuer?: string;
|
|
87
|
+
}, requestId: string): Promise<{
|
|
88
|
+
signed: boolean;
|
|
89
|
+
signature?: string;
|
|
90
|
+
publicKey?: string;
|
|
91
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export function generateCodeVerifier() {
|
|
2
|
+
const bytes = new Uint8Array(32);
|
|
3
|
+
crypto.getRandomValues(bytes);
|
|
4
|
+
return base64UrlEncode(bytes);
|
|
5
|
+
}
|
|
6
|
+
export async function generateCodeChallenge(verifier) {
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const data = encoder.encode(verifier);
|
|
9
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
10
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
11
|
+
}
|
|
12
|
+
export function generateNonce() {
|
|
13
|
+
const bytes = new Uint8Array(32);
|
|
14
|
+
crypto.getRandomValues(bytes);
|
|
15
|
+
return base64UrlEncode(bytes).slice(0, 32);
|
|
16
|
+
}
|
|
17
|
+
export function buildAuthorizeUrl(config, params = {}) {
|
|
18
|
+
const issuer = config.issuer || "https://aveid.net";
|
|
19
|
+
const search = new URLSearchParams({
|
|
20
|
+
client_id: config.clientId,
|
|
21
|
+
redirect_uri: config.redirectUri,
|
|
22
|
+
scope: (params.scope || ["openid", "profile", "email"]).join(" "),
|
|
23
|
+
state: params.state || "",
|
|
24
|
+
nonce: params.nonce || "",
|
|
25
|
+
...params.extraParams,
|
|
26
|
+
});
|
|
27
|
+
if (params.codeChallenge) {
|
|
28
|
+
search.set("code_challenge", params.codeChallenge);
|
|
29
|
+
search.set("code_challenge_method", params.codeChallengeMethod || "S256");
|
|
30
|
+
}
|
|
31
|
+
return `${issuer}/signin?${search.toString()}`;
|
|
32
|
+
}
|
|
33
|
+
export async function exchangeCode(config, payload) {
|
|
34
|
+
const apiBase = getApiBase(config.issuer);
|
|
35
|
+
const response = await fetch(`${apiBase}/api/oauth/token`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
grantType: "authorization_code",
|
|
40
|
+
code: payload.code,
|
|
41
|
+
redirectUri: config.redirectUri,
|
|
42
|
+
clientId: config.clientId,
|
|
43
|
+
codeVerifier: payload.codeVerifier,
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
throw new Error(data.error || "Failed to exchange token");
|
|
49
|
+
}
|
|
50
|
+
return response.json();
|
|
51
|
+
}
|
|
52
|
+
export async function refreshToken(config, payload) {
|
|
53
|
+
const apiBase = getApiBase(config.issuer);
|
|
54
|
+
const response = await fetch(`${apiBase}/api/oauth/token`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
grantType: "refresh_token",
|
|
59
|
+
refreshToken: payload.refreshToken,
|
|
60
|
+
clientId: config.clientId,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
throw new Error(data.error || "Failed to refresh token");
|
|
66
|
+
}
|
|
67
|
+
return response.json();
|
|
68
|
+
}
|
|
69
|
+
export async function fetchUserInfo(config, accessToken) {
|
|
70
|
+
const apiBase = getApiBase(config.issuer);
|
|
71
|
+
const response = await fetch(`${apiBase}/api/oauth/userinfo`, {
|
|
72
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
throw new Error(data.error || "Failed to fetch user info");
|
|
77
|
+
}
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
export function getApiBase(issuer) {
|
|
81
|
+
const base = issuer || "https://aveid.net";
|
|
82
|
+
return base.replace("https://aveid.net", "https://api.aveid.net");
|
|
83
|
+
}
|
|
84
|
+
function base64UrlEncode(bytes) {
|
|
85
|
+
return btoa(String.fromCharCode(...bytes))
|
|
86
|
+
.replace(/\+/g, "-")
|
|
87
|
+
.replace(/\//g, "_")
|
|
88
|
+
.replace(/=+$/, "");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a signature request for a user identity
|
|
92
|
+
* This is called from your server with client credentials
|
|
93
|
+
*/
|
|
94
|
+
export async function createSignatureRequest(config, params) {
|
|
95
|
+
const apiBase = getApiBase(config.issuer);
|
|
96
|
+
const response = await fetch(`${apiBase}/api/signing/request`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
clientId: config.clientId,
|
|
101
|
+
clientSecret: config.clientSecret,
|
|
102
|
+
identityId: params.identityId,
|
|
103
|
+
payload: params.payload,
|
|
104
|
+
metadata: params.metadata,
|
|
105
|
+
expiresInSeconds: params.expiresInSeconds || 300,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const data = await response.json();
|
|
110
|
+
throw new Error(data.error || "Failed to create signature request");
|
|
111
|
+
}
|
|
112
|
+
return response.json();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check the status of a signature request
|
|
116
|
+
*/
|
|
117
|
+
export async function getSignatureStatus(config, requestId) {
|
|
118
|
+
const apiBase = getApiBase(config.issuer);
|
|
119
|
+
const response = await fetch(`${apiBase}/api/signing/request/${requestId}/status?clientId=${encodeURIComponent(config.clientId)}`);
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
throw new Error(data.error || "Failed to get signature status");
|
|
123
|
+
}
|
|
124
|
+
return response.json();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the public signing key for an identity by handle
|
|
128
|
+
*/
|
|
129
|
+
export async function getPublicKey(config, handle) {
|
|
130
|
+
const apiBase = getApiBase(config.issuer);
|
|
131
|
+
const response = await fetch(`${apiBase}/api/signing/public-key/${encodeURIComponent(handle)}`);
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const data = await response.json();
|
|
134
|
+
throw new Error(data.error || "Failed to get public key");
|
|
135
|
+
}
|
|
136
|
+
return response.json();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Verify a signature using the Ave API
|
|
140
|
+
*/
|
|
141
|
+
export async function verifySignature(config, params) {
|
|
142
|
+
const apiBase = getApiBase(config.issuer);
|
|
143
|
+
const response = await fetch(`${apiBase}/api/signing/verify`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify(params),
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const data = await response.json();
|
|
150
|
+
throw new Error(data.error || "Failed to verify signature");
|
|
151
|
+
}
|
|
152
|
+
return response.json();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Build the URL to redirect a user for signing
|
|
156
|
+
* Use this for browser-based signing flows
|
|
157
|
+
*/
|
|
158
|
+
export function buildSigningUrl(config, requestId, options) {
|
|
159
|
+
const issuer = config.issuer || "https://aveid.net";
|
|
160
|
+
const params = new URLSearchParams({ requestId });
|
|
161
|
+
if (options?.embed) {
|
|
162
|
+
params.set("embed", "1");
|
|
163
|
+
}
|
|
164
|
+
return `${issuer}/sign?${params.toString()}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Open a popup window for signing
|
|
168
|
+
* Returns a promise that resolves when the user signs or denies
|
|
169
|
+
*/
|
|
170
|
+
export function openSigningPopup(config, requestId) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const url = buildSigningUrl(config, requestId);
|
|
173
|
+
const width = 500;
|
|
174
|
+
const height = 600;
|
|
175
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
176
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
177
|
+
const popup = window.open(url, "ave-signing", `width=${width},height=${height},left=${left},top=${top},popup=yes`);
|
|
178
|
+
if (!popup) {
|
|
179
|
+
reject(new Error("Failed to open popup - blocked by browser?"));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const handleMessage = (event) => {
|
|
183
|
+
if (event.origin !== (config.issuer || "https://aveid.net"))
|
|
184
|
+
return;
|
|
185
|
+
if (event.data?.type === "ave:signed") {
|
|
186
|
+
window.removeEventListener("message", handleMessage);
|
|
187
|
+
popup.close();
|
|
188
|
+
resolve({
|
|
189
|
+
signed: true,
|
|
190
|
+
signature: event.data.payload.signature,
|
|
191
|
+
publicKey: event.data.payload.publicKey,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (event.data?.type === "ave:denied") {
|
|
195
|
+
window.removeEventListener("message", handleMessage);
|
|
196
|
+
popup.close();
|
|
197
|
+
resolve({ signed: false });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
window.addEventListener("message", handleMessage);
|
|
201
|
+
// Check if popup was closed without action
|
|
202
|
+
const checkClosed = setInterval(() => {
|
|
203
|
+
if (popup.closed) {
|
|
204
|
+
clearInterval(checkClosed);
|
|
205
|
+
window.removeEventListener("message", handleMessage);
|
|
206
|
+
resolve({ signed: false });
|
|
207
|
+
}
|
|
208
|
+
}, 500);
|
|
209
|
+
});
|
|
210
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ServerConfig {
|
|
2
|
+
issuer?: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
redirectUri: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TokenResponse {
|
|
8
|
+
access_token: string;
|
|
9
|
+
access_token_jwt: string;
|
|
10
|
+
id_token?: string;
|
|
11
|
+
refresh_token?: string;
|
|
12
|
+
expires_in: number;
|
|
13
|
+
scope: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function exchangeCodeServer(config: ServerConfig, payload: {
|
|
16
|
+
code: string;
|
|
17
|
+
}): Promise<TokenResponse>;
|
|
18
|
+
export declare function refreshTokenServer(config: ServerConfig, payload: {
|
|
19
|
+
refreshToken: string;
|
|
20
|
+
}): Promise<TokenResponse>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export async function exchangeCodeServer(config, payload) {
|
|
2
|
+
const issuer = config.issuer || "https://aveid.net";
|
|
3
|
+
const apiBase = issuer.replace("https://aveid.net", "https://api.aveid.net");
|
|
4
|
+
const response = await fetch(`${apiBase}/api/oauth/token`, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: { "Content-Type": "application/json" },
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
grantType: "authorization_code",
|
|
9
|
+
code: payload.code,
|
|
10
|
+
redirectUri: config.redirectUri,
|
|
11
|
+
clientId: config.clientId,
|
|
12
|
+
clientSecret: config.clientSecret,
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const data = await response.json();
|
|
17
|
+
throw new Error(data.error || "Failed to exchange token");
|
|
18
|
+
}
|
|
19
|
+
return response.json();
|
|
20
|
+
}
|
|
21
|
+
export async function refreshTokenServer(config, payload) {
|
|
22
|
+
const issuer = config.issuer || "https://aveid.net";
|
|
23
|
+
const apiBase = issuer.replace("https://aveid.net", "https://api.aveid.net");
|
|
24
|
+
const response = await fetch(`${apiBase}/api/oauth/token`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
grantType: "refresh_token",
|
|
29
|
+
refreshToken: payload.refreshToken,
|
|
30
|
+
clientId: config.clientId,
|
|
31
|
+
clientSecret: config.clientSecret,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
throw new Error(data.error || "Failed to refresh token");
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
39
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type Scope = "openid" | "profile" | "email" | "offline_access" | "user_id";
|
|
2
|
+
export interface TokenResponse {
|
|
3
|
+
access_token: string;
|
|
4
|
+
access_token_jwt: string;
|
|
5
|
+
id_token?: string;
|
|
6
|
+
refresh_token?: string;
|
|
7
|
+
expires_in: number;
|
|
8
|
+
scope: string;
|
|
9
|
+
user?: {
|
|
10
|
+
id: string;
|
|
11
|
+
handle: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
avatarUrl?: string;
|
|
15
|
+
};
|
|
16
|
+
encrypted_app_key?: string;
|
|
17
|
+
user_id?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface UserInfo {
|
|
20
|
+
sub: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
preferred_username?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
picture?: string;
|
|
25
|
+
iss?: string;
|
|
26
|
+
user_id?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SignatureRequest {
|
|
29
|
+
requestId: string;
|
|
30
|
+
expiresAt: string;
|
|
31
|
+
publicKey: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SignatureResult {
|
|
34
|
+
status: "signed" | "denied" | "expired" | "pending";
|
|
35
|
+
signature?: string;
|
|
36
|
+
resolvedAt?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface SigningConfig {
|
|
39
|
+
clientId: string;
|
|
40
|
+
clientSecret: string;
|
|
41
|
+
issuer?: string;
|
|
42
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|