@insureco/bio 0.5.0 → 0.8.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/dist/{chunk-PLN6QPED.mjs → chunk-CKHMGUDP.mjs} +41 -0
- package/dist/chunk-UBURAGWI.mjs +154 -0
- package/dist/graph.d.mts +73 -1
- package/dist/graph.d.ts +73 -1
- package/dist/graph.js +41 -0
- package/dist/graph.mjs +1 -1
- package/dist/index.d.mts +99 -4
- package/dist/index.d.ts +99 -4
- package/dist/index.js +466 -2
- package/dist/index.mjs +274 -1
- package/dist/passport-react.d.mts +27 -0
- package/dist/passport-react.d.ts +27 -0
- package/dist/passport-react.js +115 -0
- package/dist/passport-react.mjs +90 -0
- package/dist/passport-types-bPgjNxv-.d.mts +79 -0
- package/dist/passport-types-bPgjNxv-.d.ts +79 -0
- package/dist/passport.d.mts +70 -0
- package/dist/passport.d.ts +70 -0
- package/dist/passport.js +180 -0
- package/dist/passport.mjs +6 -0
- package/dist/{types-Dkb-drHZ.d.mts → types-DOpXwdF2.d.mts} +117 -1
- package/dist/{types-Dkb-drHZ.d.ts → types-DOpXwdF2.d.ts} +117 -1
- package/dist/users.d.mts +1 -1
- package/dist/users.d.ts +1 -1
- package/dist/users.js +1 -0
- package/dist/users.mjs +1 -0
- package/package.json +24 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, CarrierProfile, CarrierProgram, GraphClient, GraphClientConfig, ProgramProfile } from './graph.js';
|
|
1
|
+
import { b as BioAuthConfig, A as AuthorizeOptions, c as AuthorizeResult, S as SilentAuthOptions, T as TokenResponse, d as BioUser, I as IntrospectResult, e as BioAdminConfig, U as UserFilters, f as UpdateUserData, g as BioDepartment, C as CreateDepartmentData, h as BioRole, i as CreateRoleData, j as BioOAuthClient, k as CreateClientData, E as EmbedClientConfig, l as EmbedLoginParams, m as EmbedAuthResult, n as EmbedSignupParams, o as EmbedMagicLinkParams, p as EmbedVerifyParams, q as EmbedRefreshParams, r as EmbedLogoutParams, s as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-DOpXwdF2.js';
|
|
2
|
+
export { t as AdminResponse, u as BioAddress, v as BioClientTokenPayload, w as BioMessaging, B as BioUsersConfig, x as EmbedBranding, y as EmbedUser, z as OrgMember, O as OrgMemberFilters, a as OrgMembersResult, D as ServiceRole } from './types-DOpXwdF2.js';
|
|
3
|
+
export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, AppointmentVerifyResult, CarrierProfile, CarrierProgram, GraphChainEdge, GraphChainNode, GraphChainResult, GraphClient, GraphClientConfig, GraphSearchOptions, GraphSearchResult, GraphSearchResultItem, LicenseVerifyResult, ProgramProfile, VerifyAppointmentInput, VerifyLicenseInput } from './graph.js';
|
|
4
|
+
export { PassportClient } from './passport.js';
|
|
5
|
+
export { P as Passport, b as PassportBranding, c as PassportClientConfig, d as PassportCrossOrgPermission, e as PassportEventType, f as PassportMessage, g as PassportServiceGrant, h as PassportSession, a as PassportStatus, i as PassportTokenRefresher, j as PassportVillage } from './passport-types-bPgjNxv-.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* OAuth flow client for Bio-ID SSO.
|
|
@@ -28,6 +30,14 @@ declare class BioAuth {
|
|
|
28
30
|
* Store the state and codeVerifier securely (e.g. in a cookie) for the callback.
|
|
29
31
|
*/
|
|
30
32
|
getAuthorizationUrl(opts: AuthorizeOptions): AuthorizeResult;
|
|
33
|
+
/**
|
|
34
|
+
* Build an authorization URL with prompt=none for silent authentication.
|
|
35
|
+
*
|
|
36
|
+
* Useful for checking if the user has an existing session without showing
|
|
37
|
+
* a login screen. If the user is not authenticated, Bio-ID redirects back
|
|
38
|
+
* with an `error=login_required` query parameter instead of showing UI.
|
|
39
|
+
*/
|
|
40
|
+
silentAuth(opts: SilentAuthOptions): AuthorizeResult;
|
|
31
41
|
/**
|
|
32
42
|
* Exchange an authorization code for tokens.
|
|
33
43
|
*
|
|
@@ -105,6 +115,91 @@ declare class BioAdmin {
|
|
|
105
115
|
private request;
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Client for Bio-ID headless embed endpoints.
|
|
120
|
+
*
|
|
121
|
+
* Use this when building custom login/signup UIs that authenticate
|
|
122
|
+
* directly against Bio-ID without redirects (no OAuth flow).
|
|
123
|
+
*
|
|
124
|
+
* All endpoints use X-Client-Id / X-Client-Secret header auth.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* import { EmbedClient } from '@insureco/bio'
|
|
129
|
+
*
|
|
130
|
+
* const embed = EmbedClient.fromEnv()
|
|
131
|
+
*
|
|
132
|
+
* // Login
|
|
133
|
+
* const result = await embed.login({ email: 'user@example.com', password: 'secret' })
|
|
134
|
+
* console.log(result.accessToken, result.user.bioId)
|
|
135
|
+
*
|
|
136
|
+
* // Refresh
|
|
137
|
+
* const refreshed = await embed.refresh({ refreshToken: result.refreshToken })
|
|
138
|
+
*
|
|
139
|
+
* // Logout
|
|
140
|
+
* await embed.logout({ refreshToken: result.refreshToken })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
declare class EmbedClient {
|
|
144
|
+
private readonly bioIdUrl;
|
|
145
|
+
private readonly clientId;
|
|
146
|
+
private readonly clientSecret;
|
|
147
|
+
private readonly retries;
|
|
148
|
+
private readonly timeoutMs;
|
|
149
|
+
constructor(config: EmbedClientConfig);
|
|
150
|
+
/**
|
|
151
|
+
* Create an EmbedClient from environment variables.
|
|
152
|
+
*
|
|
153
|
+
* Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
|
|
154
|
+
*/
|
|
155
|
+
static fromEnv(overrides?: Partial<EmbedClientConfig>): EmbedClient;
|
|
156
|
+
/**
|
|
157
|
+
* Authenticate a user with email and password.
|
|
158
|
+
*
|
|
159
|
+
* @param params - Email and password
|
|
160
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
161
|
+
*/
|
|
162
|
+
login(params: EmbedLoginParams): Promise<EmbedAuthResult>;
|
|
163
|
+
/**
|
|
164
|
+
* Create a new user account.
|
|
165
|
+
*
|
|
166
|
+
* @param params - Email, password, name, and optional invite token
|
|
167
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
168
|
+
*/
|
|
169
|
+
signup(params: EmbedSignupParams): Promise<EmbedAuthResult>;
|
|
170
|
+
/**
|
|
171
|
+
* Send a magic link email to the user.
|
|
172
|
+
*
|
|
173
|
+
* The user clicks the link to authenticate without a password.
|
|
174
|
+
* After sending, use `verify()` with the token from the link.
|
|
175
|
+
*
|
|
176
|
+
* @param params - Email address to send the magic link to
|
|
177
|
+
*/
|
|
178
|
+
sendMagicLink(params: EmbedMagicLinkParams): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Verify a magic link token and exchange it for auth tokens.
|
|
181
|
+
*
|
|
182
|
+
* @param params - The token from the magic link
|
|
183
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
184
|
+
*/
|
|
185
|
+
verify(params: EmbedVerifyParams): Promise<EmbedAuthResult>;
|
|
186
|
+
/**
|
|
187
|
+
* Refresh an expired access token using a refresh token.
|
|
188
|
+
*
|
|
189
|
+
* @param params - The refresh token to exchange
|
|
190
|
+
* @returns New access token, rotated refresh token, user profile, and optional branding
|
|
191
|
+
*/
|
|
192
|
+
refresh(params: EmbedRefreshParams): Promise<EmbedAuthResult>;
|
|
193
|
+
/**
|
|
194
|
+
* Revoke a refresh token (logout).
|
|
195
|
+
*
|
|
196
|
+
* @param params - The refresh token to revoke
|
|
197
|
+
*/
|
|
198
|
+
logout(params: EmbedLogoutParams): Promise<void>;
|
|
199
|
+
private embedRequest;
|
|
200
|
+
private fetchWithRetry;
|
|
201
|
+
}
|
|
202
|
+
|
|
108
203
|
/** Error thrown by Bio SDK operations */
|
|
109
204
|
declare class BioError extends Error {
|
|
110
205
|
/** HTTP status code (if from an API response) */
|
|
@@ -155,4 +250,4 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
|
|
|
155
250
|
*/
|
|
156
251
|
declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
|
|
157
252
|
|
|
158
|
-
export { AuthorizeOptions, AuthorizeResult, BioAdmin, BioAdminConfig, BioAuth, BioAuthConfig, BioDepartment, BioError, BioOAuthClient, BioRole, BioTokenPayload, BioUser, CreateClientData, CreateDepartmentData, CreateRoleData, IntrospectResult, JWKSVerifyOptions, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
|
|
253
|
+
export { AuthorizeOptions, AuthorizeResult, BioAdmin, BioAdminConfig, BioAuth, BioAuthConfig, BioDepartment, BioError, BioOAuthClient, BioRole, BioTokenPayload, BioUser, CreateClientData, CreateDepartmentData, CreateRoleData, EmbedAuthResult, EmbedClient, EmbedClientConfig, EmbedLoginParams, EmbedLogoutParams, EmbedMagicLinkParams, EmbedRefreshParams, EmbedSignupParams, EmbedVerifyParams, IntrospectResult, JWKSVerifyOptions, SilentAuthOptions, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,9 @@ __export(index_exports, {
|
|
|
33
33
|
BioAdmin: () => BioAdmin,
|
|
34
34
|
BioAuth: () => BioAuth,
|
|
35
35
|
BioError: () => BioError,
|
|
36
|
+
EmbedClient: () => EmbedClient,
|
|
36
37
|
GraphClient: () => GraphClient,
|
|
38
|
+
PassportClient: () => PassportClient,
|
|
37
39
|
decodeToken: () => decodeToken,
|
|
38
40
|
generatePKCE: () => generatePKCE,
|
|
39
41
|
isTokenExpired: () => isTokenExpired,
|
|
@@ -171,6 +173,23 @@ var BioAuth = class _BioAuth {
|
|
|
171
173
|
codeChallenge
|
|
172
174
|
};
|
|
173
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Build an authorization URL with prompt=none for silent authentication.
|
|
178
|
+
*
|
|
179
|
+
* Useful for checking if the user has an existing session without showing
|
|
180
|
+
* a login screen. If the user is not authenticated, Bio-ID redirects back
|
|
181
|
+
* with an `error=login_required` query parameter instead of showing UI.
|
|
182
|
+
*/
|
|
183
|
+
silentAuth(opts) {
|
|
184
|
+
const result = this.getAuthorizationUrl({
|
|
185
|
+
redirectUri: opts.redirectUri,
|
|
186
|
+
scopes: opts.scopes,
|
|
187
|
+
state: opts.state
|
|
188
|
+
});
|
|
189
|
+
const url = new URL(result.url);
|
|
190
|
+
url.searchParams.set("prompt", "none");
|
|
191
|
+
return { ...result, url: url.toString() };
|
|
192
|
+
}
|
|
174
193
|
/**
|
|
175
194
|
* Exchange an authorization code for tokens.
|
|
176
195
|
*
|
|
@@ -586,6 +605,257 @@ var BioAdmin = class _BioAdmin {
|
|
|
586
605
|
}
|
|
587
606
|
};
|
|
588
607
|
|
|
608
|
+
// src/embed.ts
|
|
609
|
+
var DEFAULT_BIO_URL = "https://bio.tawa.pro";
|
|
610
|
+
var DEFAULT_TIMEOUT_MS3 = 1e4;
|
|
611
|
+
var EmbedClient = class _EmbedClient {
|
|
612
|
+
bioIdUrl;
|
|
613
|
+
clientId;
|
|
614
|
+
clientSecret;
|
|
615
|
+
retries;
|
|
616
|
+
timeoutMs;
|
|
617
|
+
constructor(config) {
|
|
618
|
+
if (!config.clientId) {
|
|
619
|
+
throw new BioError("clientId is required", "config_error");
|
|
620
|
+
}
|
|
621
|
+
if (!config.clientSecret) {
|
|
622
|
+
throw new BioError("clientSecret is required", "config_error");
|
|
623
|
+
}
|
|
624
|
+
this.clientId = config.clientId;
|
|
625
|
+
this.clientSecret = config.clientSecret;
|
|
626
|
+
this.bioIdUrl = (config.bioIdUrl ?? DEFAULT_BIO_URL).replace(/\/$/, "");
|
|
627
|
+
this.retries = config.retries ?? 2;
|
|
628
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Create an EmbedClient from environment variables.
|
|
632
|
+
*
|
|
633
|
+
* Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
|
|
634
|
+
*/
|
|
635
|
+
static fromEnv(overrides) {
|
|
636
|
+
const clientId = overrides?.clientId ?? process.env.BIO_CLIENT_ID;
|
|
637
|
+
const clientSecret = overrides?.clientSecret ?? process.env.BIO_CLIENT_SECRET;
|
|
638
|
+
if (!clientId) {
|
|
639
|
+
throw new BioError(
|
|
640
|
+
"BIO_CLIENT_ID environment variable is required",
|
|
641
|
+
"config_error"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
if (!clientSecret) {
|
|
645
|
+
throw new BioError(
|
|
646
|
+
"BIO_CLIENT_SECRET environment variable is required",
|
|
647
|
+
"config_error"
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return new _EmbedClient({
|
|
651
|
+
clientId,
|
|
652
|
+
clientSecret,
|
|
653
|
+
bioIdUrl: overrides?.bioIdUrl ?? process.env.BIO_ID_URL,
|
|
654
|
+
retries: overrides?.retries,
|
|
655
|
+
timeoutMs: overrides?.timeoutMs
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Authenticate a user with email and password.
|
|
660
|
+
*
|
|
661
|
+
* @param params - Email and password
|
|
662
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
663
|
+
*/
|
|
664
|
+
async login(params) {
|
|
665
|
+
if (!params.email) throw new BioError("email is required", "validation_error");
|
|
666
|
+
if (!params.password) throw new BioError("password is required", "validation_error");
|
|
667
|
+
return this.embedRequest("/api/embed/login", {
|
|
668
|
+
email: params.email,
|
|
669
|
+
password: params.password
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Create a new user account.
|
|
674
|
+
*
|
|
675
|
+
* @param params - Email, password, name, and optional invite token
|
|
676
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
677
|
+
*/
|
|
678
|
+
async signup(params) {
|
|
679
|
+
if (!params.email) throw new BioError("email is required", "validation_error");
|
|
680
|
+
if (!params.password) throw new BioError("password is required", "validation_error");
|
|
681
|
+
if (!params.name) throw new BioError("name is required", "validation_error");
|
|
682
|
+
const body = {
|
|
683
|
+
email: params.email,
|
|
684
|
+
password: params.password,
|
|
685
|
+
name: params.name
|
|
686
|
+
};
|
|
687
|
+
if (params.inviteToken) {
|
|
688
|
+
body.inviteToken = params.inviteToken;
|
|
689
|
+
}
|
|
690
|
+
return this.embedRequest("/api/embed/signup", body);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Send a magic link email to the user.
|
|
694
|
+
*
|
|
695
|
+
* The user clicks the link to authenticate without a password.
|
|
696
|
+
* After sending, use `verify()` with the token from the link.
|
|
697
|
+
*
|
|
698
|
+
* @param params - Email address to send the magic link to
|
|
699
|
+
*/
|
|
700
|
+
async sendMagicLink(params) {
|
|
701
|
+
if (!params.email) throw new BioError("email is required", "validation_error");
|
|
702
|
+
const response = await this.fetchWithRetry(
|
|
703
|
+
"POST",
|
|
704
|
+
`${this.bioIdUrl}/api/embed/magic-link`,
|
|
705
|
+
JSON.stringify({ email: params.email })
|
|
706
|
+
);
|
|
707
|
+
const json = await parseJsonResponse(response);
|
|
708
|
+
if (!response.ok) {
|
|
709
|
+
throw new BioError(
|
|
710
|
+
extractErrorMessage(json, response.status),
|
|
711
|
+
extractErrorCode(json),
|
|
712
|
+
response.status,
|
|
713
|
+
json
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Verify a magic link token and exchange it for auth tokens.
|
|
719
|
+
*
|
|
720
|
+
* @param params - The token from the magic link
|
|
721
|
+
* @returns Access token, refresh token, user profile, and optional branding
|
|
722
|
+
*/
|
|
723
|
+
async verify(params) {
|
|
724
|
+
if (!params.token) throw new BioError("token is required", "validation_error");
|
|
725
|
+
return this.embedRequest("/api/embed/verify", {
|
|
726
|
+
token: params.token
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Refresh an expired access token using a refresh token.
|
|
731
|
+
*
|
|
732
|
+
* @param params - The refresh token to exchange
|
|
733
|
+
* @returns New access token, rotated refresh token, user profile, and optional branding
|
|
734
|
+
*/
|
|
735
|
+
async refresh(params) {
|
|
736
|
+
if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
|
|
737
|
+
return this.embedRequest("/api/embed/refresh", {
|
|
738
|
+
refreshToken: params.refreshToken
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Revoke a refresh token (logout).
|
|
743
|
+
*
|
|
744
|
+
* @param params - The refresh token to revoke
|
|
745
|
+
*/
|
|
746
|
+
async logout(params) {
|
|
747
|
+
if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
|
|
748
|
+
const response = await this.fetchWithRetry(
|
|
749
|
+
"POST",
|
|
750
|
+
`${this.bioIdUrl}/api/embed/logout`,
|
|
751
|
+
JSON.stringify({ refreshToken: params.refreshToken })
|
|
752
|
+
);
|
|
753
|
+
const json = await parseJsonResponse(response);
|
|
754
|
+
if (!response.ok) {
|
|
755
|
+
throw new BioError(
|
|
756
|
+
extractErrorMessage(json, response.status),
|
|
757
|
+
extractErrorCode(json),
|
|
758
|
+
response.status,
|
|
759
|
+
json
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// ── Private helpers ──────────────────────────────────────────────────────
|
|
764
|
+
async embedRequest(path, body) {
|
|
765
|
+
const response = await this.fetchWithRetry(
|
|
766
|
+
"POST",
|
|
767
|
+
`${this.bioIdUrl}${path}`,
|
|
768
|
+
JSON.stringify(body)
|
|
769
|
+
);
|
|
770
|
+
const json = await parseJsonResponse(response);
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
throw new BioError(
|
|
773
|
+
extractErrorMessage(json, response.status),
|
|
774
|
+
extractErrorCode(json),
|
|
775
|
+
response.status,
|
|
776
|
+
json
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
return mapEmbedResponse(json);
|
|
780
|
+
}
|
|
781
|
+
async fetchWithRetry(method, url, body, attempt = 0) {
|
|
782
|
+
try {
|
|
783
|
+
const response = await fetch(url, {
|
|
784
|
+
method,
|
|
785
|
+
headers: {
|
|
786
|
+
"Content-Type": "application/json",
|
|
787
|
+
"X-Client-Id": this.clientId,
|
|
788
|
+
"X-Client-Secret": this.clientSecret
|
|
789
|
+
},
|
|
790
|
+
body,
|
|
791
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
792
|
+
});
|
|
793
|
+
if (response.status >= 500 && attempt < this.retries) {
|
|
794
|
+
await sleep(retryDelay(attempt));
|
|
795
|
+
return this.fetchWithRetry(method, url, body, attempt + 1);
|
|
796
|
+
}
|
|
797
|
+
return response;
|
|
798
|
+
} catch (err) {
|
|
799
|
+
if (attempt < this.retries) {
|
|
800
|
+
await sleep(retryDelay(attempt));
|
|
801
|
+
return this.fetchWithRetry(method, url, body, attempt + 1);
|
|
802
|
+
}
|
|
803
|
+
const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
|
|
804
|
+
throw new BioError(
|
|
805
|
+
isTimeout ? `Request timed out after ${this.timeoutMs}ms` : err instanceof Error ? err.message : "Network error",
|
|
806
|
+
isTimeout ? "timeout" : "network_error"
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
function mapEmbedResponse(raw) {
|
|
812
|
+
const data = raw.data ?? raw;
|
|
813
|
+
const rawUser = data.user ?? {};
|
|
814
|
+
const rawBranding = data.branding;
|
|
815
|
+
const user = {
|
|
816
|
+
bioId: rawUser.bioId,
|
|
817
|
+
email: rawUser.email,
|
|
818
|
+
name: rawUser.name,
|
|
819
|
+
orgSlug: rawUser.orgSlug
|
|
820
|
+
};
|
|
821
|
+
const result = {
|
|
822
|
+
accessToken: data.access_token,
|
|
823
|
+
refreshToken: data.refresh_token,
|
|
824
|
+
tokenType: data.token_type ?? "Bearer",
|
|
825
|
+
expiresIn: data.expires_in,
|
|
826
|
+
user
|
|
827
|
+
};
|
|
828
|
+
if (rawBranding) {
|
|
829
|
+
result.branding = {
|
|
830
|
+
displayName: rawBranding.displayName,
|
|
831
|
+
logoUrl: rawBranding.logoUrl,
|
|
832
|
+
logoMarkUrl: rawBranding.logoMarkUrl,
|
|
833
|
+
primaryColor: rawBranding.primaryColor,
|
|
834
|
+
secondaryColor: rawBranding.secondaryColor,
|
|
835
|
+
verified: rawBranding.verified,
|
|
836
|
+
whiteLabelApproved: rawBranding.whiteLabelApproved
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
function extractErrorMessage(json, status) {
|
|
842
|
+
const error = json.error;
|
|
843
|
+
if (typeof error === "object" && error !== null) {
|
|
844
|
+
return error.message ?? `Embed API returned ${status}`;
|
|
845
|
+
}
|
|
846
|
+
if (typeof error === "string") {
|
|
847
|
+
return error;
|
|
848
|
+
}
|
|
849
|
+
return `Embed API returned ${status}`;
|
|
850
|
+
}
|
|
851
|
+
function extractErrorCode(json) {
|
|
852
|
+
const error = json.error;
|
|
853
|
+
if (typeof error === "object" && error !== null) {
|
|
854
|
+
return error.code ?? "embed_error";
|
|
855
|
+
}
|
|
856
|
+
return "embed_error";
|
|
857
|
+
}
|
|
858
|
+
|
|
589
859
|
// src/jwt.ts
|
|
590
860
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
591
861
|
var DEFAULT_ISSUERS = [
|
|
@@ -740,7 +1010,7 @@ async function verifyTokenJWKS(token, options) {
|
|
|
740
1010
|
}
|
|
741
1011
|
|
|
742
1012
|
// src/graph.ts
|
|
743
|
-
var
|
|
1013
|
+
var DEFAULT_TIMEOUT_MS4 = 1e4;
|
|
744
1014
|
var BIO_GRAPH_URL = "https://bio-graph.tawa.pro";
|
|
745
1015
|
var GraphClient = class _GraphClient {
|
|
746
1016
|
graphUrl;
|
|
@@ -749,7 +1019,7 @@ var GraphClient = class _GraphClient {
|
|
|
749
1019
|
constructor(config = {}) {
|
|
750
1020
|
this.graphUrl = (config.graphUrl ?? process.env.BIO_GRAPH_URL ?? BIO_GRAPH_URL).replace(/\/$/, "");
|
|
751
1021
|
this.accessToken = config.accessToken;
|
|
752
|
-
this.timeoutMs = config.timeoutMs ??
|
|
1022
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
|
|
753
1023
|
}
|
|
754
1024
|
/**
|
|
755
1025
|
* Create a GraphClient from environment variables.
|
|
@@ -798,7 +1068,48 @@ var GraphClient = class _GraphClient {
|
|
|
798
1068
|
async matchAppetite(input) {
|
|
799
1069
|
return this.post("/api/graph/appetite/match", input, { requiresAuth: true });
|
|
800
1070
|
}
|
|
1071
|
+
// ─── Verification Routes (auth required, gas-metered) ─────────────────────
|
|
1072
|
+
/**
|
|
1073
|
+
* Verify an agent's license for a given state and line of business.
|
|
1074
|
+
* Returns validity, license number, expiry, and a Septor proof hash.
|
|
1075
|
+
*/
|
|
1076
|
+
async verifyLicense(input) {
|
|
1077
|
+
return this.post("/api/graph/verify/license", input, { requiresAuth: true });
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Verify an agent's appointment with a carrier.
|
|
1081
|
+
* Returns validity, appointed date, states, and a Septor proof hash.
|
|
1082
|
+
*/
|
|
1083
|
+
async verifyAppointment(input) {
|
|
1084
|
+
return this.post("/api/graph/verify/appointment", input, { requiresAuth: true });
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Get the distribution chain for an entity (agent → agency → MGA → carrier).
|
|
1088
|
+
* Requires an accessToken (auth: required).
|
|
1089
|
+
*/
|
|
1090
|
+
async getChain(entityId) {
|
|
1091
|
+
return this.getAuth(`/api/graph/chain/${encodeURIComponent(entityId)}`);
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Search across Agent, Agency, MGA, and Carrier nodes.
|
|
1095
|
+
* Public route — no auth required.
|
|
1096
|
+
*/
|
|
1097
|
+
async search(query, options) {
|
|
1098
|
+
const params = new URLSearchParams({ q: query });
|
|
1099
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
1100
|
+
if (options?.types?.length) params.set("types", options.types.join(","));
|
|
1101
|
+
return this.get(`/api/graph/search?${params.toString()}`);
|
|
1102
|
+
}
|
|
801
1103
|
// ─── Internal ─────────────────────────────────────────────────────────────
|
|
1104
|
+
async getAuth(path) {
|
|
1105
|
+
if (!this.accessToken) {
|
|
1106
|
+
throw new BioError(
|
|
1107
|
+
`bio-graph ${path} requires an accessToken \u2014 pass it in the GraphClient constructor`,
|
|
1108
|
+
"config_error"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return this.get(path);
|
|
1112
|
+
}
|
|
802
1113
|
async get(path, attempt = 0) {
|
|
803
1114
|
const url = `${this.graphUrl}${path}`;
|
|
804
1115
|
const controller = new AbortController();
|
|
@@ -897,12 +1208,165 @@ var GraphClient = class _GraphClient {
|
|
|
897
1208
|
return body;
|
|
898
1209
|
}
|
|
899
1210
|
};
|
|
1211
|
+
|
|
1212
|
+
// src/passport-client.ts
|
|
1213
|
+
var PassportClient = class {
|
|
1214
|
+
config;
|
|
1215
|
+
ws = null;
|
|
1216
|
+
_passport = null;
|
|
1217
|
+
_status = "disconnected";
|
|
1218
|
+
reconnectAttempt = 0;
|
|
1219
|
+
reconnectTimer = null;
|
|
1220
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1221
|
+
closed = false;
|
|
1222
|
+
constructor(config) {
|
|
1223
|
+
this.config = {
|
|
1224
|
+
autoReconnect: true,
|
|
1225
|
+
maxReconnectDelay: 3e4,
|
|
1226
|
+
...config
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/** Current passport object (null until first identity message) */
|
|
1230
|
+
get passport() {
|
|
1231
|
+
return this._passport;
|
|
1232
|
+
}
|
|
1233
|
+
/** Current connection status */
|
|
1234
|
+
get status() {
|
|
1235
|
+
return this._status;
|
|
1236
|
+
}
|
|
1237
|
+
/** Connect to the passport WebSocket */
|
|
1238
|
+
connect() {
|
|
1239
|
+
if (this.closed) return;
|
|
1240
|
+
this.setStatus("connecting");
|
|
1241
|
+
const url = new URL("/passport", this.config.bioIdUrl.replace(/^http/, "ws"));
|
|
1242
|
+
url.searchParams.set("token", this.config.accessToken);
|
|
1243
|
+
url.searchParams.set("version", "1");
|
|
1244
|
+
if (this.config.service) {
|
|
1245
|
+
url.searchParams.set("service", this.config.service);
|
|
1246
|
+
}
|
|
1247
|
+
this.ws = new WebSocket(url.toString());
|
|
1248
|
+
this.ws.onopen = () => {
|
|
1249
|
+
this.reconnectAttempt = 0;
|
|
1250
|
+
this.setStatus("connected");
|
|
1251
|
+
this.emit("connected", this._status);
|
|
1252
|
+
};
|
|
1253
|
+
this.ws.onmessage = (event) => {
|
|
1254
|
+
try {
|
|
1255
|
+
const data = JSON.parse(
|
|
1256
|
+
typeof event.data === "string" ? event.data : ""
|
|
1257
|
+
);
|
|
1258
|
+
if (data.type === "identity" || data.type === "passport_updated") {
|
|
1259
|
+
this._passport = data.passport ?? null;
|
|
1260
|
+
this.emit(data.type, this._passport);
|
|
1261
|
+
} else if (data.type === "revoked") {
|
|
1262
|
+
this._passport = null;
|
|
1263
|
+
this.emit("revoked");
|
|
1264
|
+
}
|
|
1265
|
+
} catch {
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
this.ws.onclose = (event) => {
|
|
1269
|
+
this.setStatus("disconnected");
|
|
1270
|
+
this.emit("disconnected", this._status);
|
|
1271
|
+
if (event?.code === 4003 && this.config.refreshToken && this.config.bioAuth) {
|
|
1272
|
+
this.handleTokenRefresh();
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
this.scheduleReconnect();
|
|
1276
|
+
};
|
|
1277
|
+
this.ws.onerror = () => {
|
|
1278
|
+
const error = new Error("Passport WebSocket error");
|
|
1279
|
+
this.emit("error", error);
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
/** Subscribe to an event */
|
|
1283
|
+
on(event, handler) {
|
|
1284
|
+
if (!this.listeners.has(event)) {
|
|
1285
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1286
|
+
}
|
|
1287
|
+
this.listeners.get(event).add(handler);
|
|
1288
|
+
}
|
|
1289
|
+
/** Unsubscribe from an event */
|
|
1290
|
+
off(event, handler) {
|
|
1291
|
+
this.listeners.get(event)?.delete(handler);
|
|
1292
|
+
}
|
|
1293
|
+
/** Disconnect and stop reconnecting */
|
|
1294
|
+
close() {
|
|
1295
|
+
this.closed = true;
|
|
1296
|
+
if (this.reconnectTimer) {
|
|
1297
|
+
clearTimeout(this.reconnectTimer);
|
|
1298
|
+
this.reconnectTimer = null;
|
|
1299
|
+
}
|
|
1300
|
+
if (this.ws) {
|
|
1301
|
+
this.ws.onclose = null;
|
|
1302
|
+
this.ws.close();
|
|
1303
|
+
this.ws = null;
|
|
1304
|
+
}
|
|
1305
|
+
this.setStatus("disconnected");
|
|
1306
|
+
}
|
|
1307
|
+
/** Update access token (e.g. after refresh) and reconnect */
|
|
1308
|
+
updateToken(accessToken) {
|
|
1309
|
+
this.config.accessToken = accessToken;
|
|
1310
|
+
if (this.ws) {
|
|
1311
|
+
this.ws.onclose = null;
|
|
1312
|
+
this.ws.close();
|
|
1313
|
+
this.ws = null;
|
|
1314
|
+
}
|
|
1315
|
+
this.reconnectAttempt = 0;
|
|
1316
|
+
this.connect();
|
|
1317
|
+
}
|
|
1318
|
+
async handleTokenRefresh() {
|
|
1319
|
+
if (!this.config.refreshToken || !this.config.bioAuth) return;
|
|
1320
|
+
try {
|
|
1321
|
+
const tokens = await this.config.bioAuth.refreshToken(this.config.refreshToken);
|
|
1322
|
+
this.config.accessToken = tokens.access_token;
|
|
1323
|
+
if (tokens.refresh_token) {
|
|
1324
|
+
this.config.refreshToken = tokens.refresh_token;
|
|
1325
|
+
}
|
|
1326
|
+
if (this.config.onTokenRefresh) {
|
|
1327
|
+
this.config.onTokenRefresh(tokens);
|
|
1328
|
+
}
|
|
1329
|
+
this.reconnectAttempt = 0;
|
|
1330
|
+
this.connect();
|
|
1331
|
+
} catch {
|
|
1332
|
+
this.emit("error", new Error("Token refresh failed"));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
setStatus(status) {
|
|
1336
|
+
this._status = status;
|
|
1337
|
+
}
|
|
1338
|
+
emit(event, ...args) {
|
|
1339
|
+
const handlers = this.listeners.get(event);
|
|
1340
|
+
if (handlers) {
|
|
1341
|
+
for (const handler of handlers) {
|
|
1342
|
+
try {
|
|
1343
|
+
handler(...args);
|
|
1344
|
+
} catch {
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
scheduleReconnect() {
|
|
1350
|
+
if (this.closed || !this.config.autoReconnect) return;
|
|
1351
|
+
const delay = Math.min(
|
|
1352
|
+
1e3 * Math.pow(2, this.reconnectAttempt),
|
|
1353
|
+
this.config.maxReconnectDelay ?? 3e4
|
|
1354
|
+
);
|
|
1355
|
+
this.reconnectAttempt++;
|
|
1356
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1357
|
+
this.reconnectTimer = null;
|
|
1358
|
+
this.connect();
|
|
1359
|
+
}, delay);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
900
1362
|
// Annotate the CommonJS export names for ESM import in node:
|
|
901
1363
|
0 && (module.exports = {
|
|
902
1364
|
BioAdmin,
|
|
903
1365
|
BioAuth,
|
|
904
1366
|
BioError,
|
|
1367
|
+
EmbedClient,
|
|
905
1368
|
GraphClient,
|
|
1369
|
+
PassportClient,
|
|
906
1370
|
decodeToken,
|
|
907
1371
|
generatePKCE,
|
|
908
1372
|
isTokenExpired,
|