@insureco/bio 0.5.0 → 0.6.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/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BioAuthConfig, A as AuthorizeOptions, a as AuthorizeResult, T as TokenResponse, b as BioUser, I as IntrospectResult, c as BioAdminConfig, U as UserFilters, d as UpdateUserData, e as BioDepartment, C as CreateDepartmentData, f as BioRole, g as CreateRoleData, h as BioOAuthClient, i as CreateClientData, j as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-Dkb-drHZ.mjs';
2
- export { k as AdminResponse, l as BioAddress, m as BioClientTokenPayload, n as BioMessaging, o as BioUsersConfig, O as OrgMember, p as OrgMemberFilters, q as OrgMembersResult } from './types-Dkb-drHZ.mjs';
1
+ import { B as BioAuthConfig, A as AuthorizeOptions, a as AuthorizeResult, T as TokenResponse, b as BioUser, I as IntrospectResult, c as BioAdminConfig, U as UserFilters, d as UpdateUserData, e as BioDepartment, C as CreateDepartmentData, f as BioRole, g as CreateRoleData, h as BioOAuthClient, i as CreateClientData, E as EmbedClientConfig, j as EmbedLoginParams, k as EmbedAuthResult, l as EmbedSignupParams, m as EmbedMagicLinkParams, n as EmbedVerifyParams, o as EmbedRefreshParams, p as EmbedLogoutParams, q as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-CJe1FP61.mjs';
2
+ export { r as AdminResponse, s as BioAddress, t as BioClientTokenPayload, u as BioMessaging, v as BioUsersConfig, w as EmbedBranding, x as EmbedUser, O as OrgMember, y as OrgMemberFilters, z as OrgMembersResult } from './types-CJe1FP61.mjs';
3
3
  export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, CarrierProfile, CarrierProgram, GraphClient, GraphClientConfig, ProgramProfile } from './graph.mjs';
4
4
 
5
5
  /**
@@ -105,6 +105,91 @@ declare class BioAdmin {
105
105
  private request;
106
106
  }
107
107
 
108
+ /**
109
+ * Client for Bio-ID headless embed endpoints.
110
+ *
111
+ * Use this when building custom login/signup UIs that authenticate
112
+ * directly against Bio-ID without redirects (no OAuth flow).
113
+ *
114
+ * All endpoints use X-Client-Id / X-Client-Secret header auth.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * import { EmbedClient } from '@insureco/bio'
119
+ *
120
+ * const embed = EmbedClient.fromEnv()
121
+ *
122
+ * // Login
123
+ * const result = await embed.login({ email: 'user@example.com', password: 'secret' })
124
+ * console.log(result.accessToken, result.user.bioId)
125
+ *
126
+ * // Refresh
127
+ * const refreshed = await embed.refresh({ refreshToken: result.refreshToken })
128
+ *
129
+ * // Logout
130
+ * await embed.logout({ refreshToken: result.refreshToken })
131
+ * ```
132
+ */
133
+ declare class EmbedClient {
134
+ private readonly bioIdUrl;
135
+ private readonly clientId;
136
+ private readonly clientSecret;
137
+ private readonly retries;
138
+ private readonly timeoutMs;
139
+ constructor(config: EmbedClientConfig);
140
+ /**
141
+ * Create an EmbedClient from environment variables.
142
+ *
143
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
144
+ */
145
+ static fromEnv(overrides?: Partial<EmbedClientConfig>): EmbedClient;
146
+ /**
147
+ * Authenticate a user with email and password.
148
+ *
149
+ * @param params - Email and password
150
+ * @returns Access token, refresh token, user profile, and optional branding
151
+ */
152
+ login(params: EmbedLoginParams): Promise<EmbedAuthResult>;
153
+ /**
154
+ * Create a new user account.
155
+ *
156
+ * @param params - Email, password, name, and optional invite token
157
+ * @returns Access token, refresh token, user profile, and optional branding
158
+ */
159
+ signup(params: EmbedSignupParams): Promise<EmbedAuthResult>;
160
+ /**
161
+ * Send a magic link email to the user.
162
+ *
163
+ * The user clicks the link to authenticate without a password.
164
+ * After sending, use `verify()` with the token from the link.
165
+ *
166
+ * @param params - Email address to send the magic link to
167
+ */
168
+ sendMagicLink(params: EmbedMagicLinkParams): Promise<void>;
169
+ /**
170
+ * Verify a magic link token and exchange it for auth tokens.
171
+ *
172
+ * @param params - The token from the magic link
173
+ * @returns Access token, refresh token, user profile, and optional branding
174
+ */
175
+ verify(params: EmbedVerifyParams): Promise<EmbedAuthResult>;
176
+ /**
177
+ * Refresh an expired access token using a refresh token.
178
+ *
179
+ * @param params - The refresh token to exchange
180
+ * @returns New access token, rotated refresh token, user profile, and optional branding
181
+ */
182
+ refresh(params: EmbedRefreshParams): Promise<EmbedAuthResult>;
183
+ /**
184
+ * Revoke a refresh token (logout).
185
+ *
186
+ * @param params - The refresh token to revoke
187
+ */
188
+ logout(params: EmbedLogoutParams): Promise<void>;
189
+ private embedRequest;
190
+ private fetchWithRetry;
191
+ }
192
+
108
193
  /** Error thrown by Bio SDK operations */
109
194
  declare class BioError extends Error {
110
195
  /** HTTP status code (if from an API response) */
@@ -155,4 +240,4 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
155
240
  */
156
241
  declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
157
242
 
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 };
243
+ 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, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BioAuthConfig, A as AuthorizeOptions, a as AuthorizeResult, T as TokenResponse, b as BioUser, I as IntrospectResult, c as BioAdminConfig, U as UserFilters, d as UpdateUserData, e as BioDepartment, C as CreateDepartmentData, f as BioRole, g as CreateRoleData, h as BioOAuthClient, i as CreateClientData, j as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-Dkb-drHZ.js';
2
- export { k as AdminResponse, l as BioAddress, m as BioClientTokenPayload, n as BioMessaging, o as BioUsersConfig, O as OrgMember, p as OrgMemberFilters, q as OrgMembersResult } from './types-Dkb-drHZ.js';
1
+ import { B as BioAuthConfig, A as AuthorizeOptions, a as AuthorizeResult, T as TokenResponse, b as BioUser, I as IntrospectResult, c as BioAdminConfig, U as UserFilters, d as UpdateUserData, e as BioDepartment, C as CreateDepartmentData, f as BioRole, g as CreateRoleData, h as BioOAuthClient, i as CreateClientData, E as EmbedClientConfig, j as EmbedLoginParams, k as EmbedAuthResult, l as EmbedSignupParams, m as EmbedMagicLinkParams, n as EmbedVerifyParams, o as EmbedRefreshParams, p as EmbedLogoutParams, q as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-CJe1FP61.js';
2
+ export { r as AdminResponse, s as BioAddress, t as BioClientTokenPayload, u as BioMessaging, v as BioUsersConfig, w as EmbedBranding, x as EmbedUser, O as OrgMember, y as OrgMemberFilters, z as OrgMembersResult } from './types-CJe1FP61.js';
3
3
  export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, CarrierProfile, CarrierProgram, GraphClient, GraphClientConfig, ProgramProfile } from './graph.js';
4
4
 
5
5
  /**
@@ -105,6 +105,91 @@ declare class BioAdmin {
105
105
  private request;
106
106
  }
107
107
 
108
+ /**
109
+ * Client for Bio-ID headless embed endpoints.
110
+ *
111
+ * Use this when building custom login/signup UIs that authenticate
112
+ * directly against Bio-ID without redirects (no OAuth flow).
113
+ *
114
+ * All endpoints use X-Client-Id / X-Client-Secret header auth.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * import { EmbedClient } from '@insureco/bio'
119
+ *
120
+ * const embed = EmbedClient.fromEnv()
121
+ *
122
+ * // Login
123
+ * const result = await embed.login({ email: 'user@example.com', password: 'secret' })
124
+ * console.log(result.accessToken, result.user.bioId)
125
+ *
126
+ * // Refresh
127
+ * const refreshed = await embed.refresh({ refreshToken: result.refreshToken })
128
+ *
129
+ * // Logout
130
+ * await embed.logout({ refreshToken: result.refreshToken })
131
+ * ```
132
+ */
133
+ declare class EmbedClient {
134
+ private readonly bioIdUrl;
135
+ private readonly clientId;
136
+ private readonly clientSecret;
137
+ private readonly retries;
138
+ private readonly timeoutMs;
139
+ constructor(config: EmbedClientConfig);
140
+ /**
141
+ * Create an EmbedClient from environment variables.
142
+ *
143
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
144
+ */
145
+ static fromEnv(overrides?: Partial<EmbedClientConfig>): EmbedClient;
146
+ /**
147
+ * Authenticate a user with email and password.
148
+ *
149
+ * @param params - Email and password
150
+ * @returns Access token, refresh token, user profile, and optional branding
151
+ */
152
+ login(params: EmbedLoginParams): Promise<EmbedAuthResult>;
153
+ /**
154
+ * Create a new user account.
155
+ *
156
+ * @param params - Email, password, name, and optional invite token
157
+ * @returns Access token, refresh token, user profile, and optional branding
158
+ */
159
+ signup(params: EmbedSignupParams): Promise<EmbedAuthResult>;
160
+ /**
161
+ * Send a magic link email to the user.
162
+ *
163
+ * The user clicks the link to authenticate without a password.
164
+ * After sending, use `verify()` with the token from the link.
165
+ *
166
+ * @param params - Email address to send the magic link to
167
+ */
168
+ sendMagicLink(params: EmbedMagicLinkParams): Promise<void>;
169
+ /**
170
+ * Verify a magic link token and exchange it for auth tokens.
171
+ *
172
+ * @param params - The token from the magic link
173
+ * @returns Access token, refresh token, user profile, and optional branding
174
+ */
175
+ verify(params: EmbedVerifyParams): Promise<EmbedAuthResult>;
176
+ /**
177
+ * Refresh an expired access token using a refresh token.
178
+ *
179
+ * @param params - The refresh token to exchange
180
+ * @returns New access token, rotated refresh token, user profile, and optional branding
181
+ */
182
+ refresh(params: EmbedRefreshParams): Promise<EmbedAuthResult>;
183
+ /**
184
+ * Revoke a refresh token (logout).
185
+ *
186
+ * @param params - The refresh token to revoke
187
+ */
188
+ logout(params: EmbedLogoutParams): Promise<void>;
189
+ private embedRequest;
190
+ private fetchWithRetry;
191
+ }
192
+
108
193
  /** Error thrown by Bio SDK operations */
109
194
  declare class BioError extends Error {
110
195
  /** HTTP status code (if from an API response) */
@@ -155,4 +240,4 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
155
240
  */
156
241
  declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
157
242
 
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 };
243
+ 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, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  BioAdmin: () => BioAdmin,
34
34
  BioAuth: () => BioAuth,
35
35
  BioError: () => BioError,
36
+ EmbedClient: () => EmbedClient,
36
37
  GraphClient: () => GraphClient,
37
38
  decodeToken: () => decodeToken,
38
39
  generatePKCE: () => generatePKCE,
@@ -586,6 +587,257 @@ var BioAdmin = class _BioAdmin {
586
587
  }
587
588
  };
588
589
 
590
+ // src/embed.ts
591
+ var DEFAULT_BIO_URL = "https://bio.tawa.pro";
592
+ var DEFAULT_TIMEOUT_MS3 = 1e4;
593
+ var EmbedClient = class _EmbedClient {
594
+ bioIdUrl;
595
+ clientId;
596
+ clientSecret;
597
+ retries;
598
+ timeoutMs;
599
+ constructor(config) {
600
+ if (!config.clientId) {
601
+ throw new BioError("clientId is required", "config_error");
602
+ }
603
+ if (!config.clientSecret) {
604
+ throw new BioError("clientSecret is required", "config_error");
605
+ }
606
+ this.clientId = config.clientId;
607
+ this.clientSecret = config.clientSecret;
608
+ this.bioIdUrl = (config.bioIdUrl ?? DEFAULT_BIO_URL).replace(/\/$/, "");
609
+ this.retries = config.retries ?? 2;
610
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
611
+ }
612
+ /**
613
+ * Create an EmbedClient from environment variables.
614
+ *
615
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
616
+ */
617
+ static fromEnv(overrides) {
618
+ const clientId = overrides?.clientId ?? process.env.BIO_CLIENT_ID;
619
+ const clientSecret = overrides?.clientSecret ?? process.env.BIO_CLIENT_SECRET;
620
+ if (!clientId) {
621
+ throw new BioError(
622
+ "BIO_CLIENT_ID environment variable is required",
623
+ "config_error"
624
+ );
625
+ }
626
+ if (!clientSecret) {
627
+ throw new BioError(
628
+ "BIO_CLIENT_SECRET environment variable is required",
629
+ "config_error"
630
+ );
631
+ }
632
+ return new _EmbedClient({
633
+ clientId,
634
+ clientSecret,
635
+ bioIdUrl: overrides?.bioIdUrl ?? process.env.BIO_ID_URL,
636
+ retries: overrides?.retries,
637
+ timeoutMs: overrides?.timeoutMs
638
+ });
639
+ }
640
+ /**
641
+ * Authenticate a user with email and password.
642
+ *
643
+ * @param params - Email and password
644
+ * @returns Access token, refresh token, user profile, and optional branding
645
+ */
646
+ async login(params) {
647
+ if (!params.email) throw new BioError("email is required", "validation_error");
648
+ if (!params.password) throw new BioError("password is required", "validation_error");
649
+ return this.embedRequest("/api/embed/login", {
650
+ email: params.email,
651
+ password: params.password
652
+ });
653
+ }
654
+ /**
655
+ * Create a new user account.
656
+ *
657
+ * @param params - Email, password, name, and optional invite token
658
+ * @returns Access token, refresh token, user profile, and optional branding
659
+ */
660
+ async signup(params) {
661
+ if (!params.email) throw new BioError("email is required", "validation_error");
662
+ if (!params.password) throw new BioError("password is required", "validation_error");
663
+ if (!params.name) throw new BioError("name is required", "validation_error");
664
+ const body = {
665
+ email: params.email,
666
+ password: params.password,
667
+ name: params.name
668
+ };
669
+ if (params.inviteToken) {
670
+ body.inviteToken = params.inviteToken;
671
+ }
672
+ return this.embedRequest("/api/embed/signup", body);
673
+ }
674
+ /**
675
+ * Send a magic link email to the user.
676
+ *
677
+ * The user clicks the link to authenticate without a password.
678
+ * After sending, use `verify()` with the token from the link.
679
+ *
680
+ * @param params - Email address to send the magic link to
681
+ */
682
+ async sendMagicLink(params) {
683
+ if (!params.email) throw new BioError("email is required", "validation_error");
684
+ const response = await this.fetchWithRetry(
685
+ "POST",
686
+ `${this.bioIdUrl}/api/embed/magic-link`,
687
+ JSON.stringify({ email: params.email })
688
+ );
689
+ const json = await parseJsonResponse(response);
690
+ if (!response.ok) {
691
+ throw new BioError(
692
+ extractErrorMessage(json, response.status),
693
+ extractErrorCode(json),
694
+ response.status,
695
+ json
696
+ );
697
+ }
698
+ }
699
+ /**
700
+ * Verify a magic link token and exchange it for auth tokens.
701
+ *
702
+ * @param params - The token from the magic link
703
+ * @returns Access token, refresh token, user profile, and optional branding
704
+ */
705
+ async verify(params) {
706
+ if (!params.token) throw new BioError("token is required", "validation_error");
707
+ return this.embedRequest("/api/embed/verify", {
708
+ token: params.token
709
+ });
710
+ }
711
+ /**
712
+ * Refresh an expired access token using a refresh token.
713
+ *
714
+ * @param params - The refresh token to exchange
715
+ * @returns New access token, rotated refresh token, user profile, and optional branding
716
+ */
717
+ async refresh(params) {
718
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
719
+ return this.embedRequest("/api/embed/refresh", {
720
+ refreshToken: params.refreshToken
721
+ });
722
+ }
723
+ /**
724
+ * Revoke a refresh token (logout).
725
+ *
726
+ * @param params - The refresh token to revoke
727
+ */
728
+ async logout(params) {
729
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
730
+ const response = await this.fetchWithRetry(
731
+ "POST",
732
+ `${this.bioIdUrl}/api/embed/logout`,
733
+ JSON.stringify({ refreshToken: params.refreshToken })
734
+ );
735
+ const json = await parseJsonResponse(response);
736
+ if (!response.ok) {
737
+ throw new BioError(
738
+ extractErrorMessage(json, response.status),
739
+ extractErrorCode(json),
740
+ response.status,
741
+ json
742
+ );
743
+ }
744
+ }
745
+ // ── Private helpers ──────────────────────────────────────────────────────
746
+ async embedRequest(path, body) {
747
+ const response = await this.fetchWithRetry(
748
+ "POST",
749
+ `${this.bioIdUrl}${path}`,
750
+ JSON.stringify(body)
751
+ );
752
+ const json = await parseJsonResponse(response);
753
+ if (!response.ok) {
754
+ throw new BioError(
755
+ extractErrorMessage(json, response.status),
756
+ extractErrorCode(json),
757
+ response.status,
758
+ json
759
+ );
760
+ }
761
+ return mapEmbedResponse(json);
762
+ }
763
+ async fetchWithRetry(method, url, body, attempt = 0) {
764
+ try {
765
+ const response = await fetch(url, {
766
+ method,
767
+ headers: {
768
+ "Content-Type": "application/json",
769
+ "X-Client-Id": this.clientId,
770
+ "X-Client-Secret": this.clientSecret
771
+ },
772
+ body,
773
+ signal: AbortSignal.timeout(this.timeoutMs)
774
+ });
775
+ if (response.status >= 500 && attempt < this.retries) {
776
+ await sleep(retryDelay(attempt));
777
+ return this.fetchWithRetry(method, url, body, attempt + 1);
778
+ }
779
+ return response;
780
+ } catch (err) {
781
+ if (attempt < this.retries) {
782
+ await sleep(retryDelay(attempt));
783
+ return this.fetchWithRetry(method, url, body, attempt + 1);
784
+ }
785
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
786
+ throw new BioError(
787
+ isTimeout ? `Request timed out after ${this.timeoutMs}ms` : err instanceof Error ? err.message : "Network error",
788
+ isTimeout ? "timeout" : "network_error"
789
+ );
790
+ }
791
+ }
792
+ };
793
+ function mapEmbedResponse(raw) {
794
+ const data = raw.data ?? raw;
795
+ const rawUser = data.user ?? {};
796
+ const rawBranding = data.branding;
797
+ const user = {
798
+ bioId: rawUser.bioId,
799
+ email: rawUser.email,
800
+ name: rawUser.name,
801
+ orgSlug: rawUser.orgSlug
802
+ };
803
+ const result = {
804
+ accessToken: data.access_token,
805
+ refreshToken: data.refresh_token,
806
+ tokenType: data.token_type ?? "Bearer",
807
+ expiresIn: data.expires_in,
808
+ user
809
+ };
810
+ if (rawBranding) {
811
+ result.branding = {
812
+ displayName: rawBranding.displayName,
813
+ logoUrl: rawBranding.logoUrl,
814
+ logoMarkUrl: rawBranding.logoMarkUrl,
815
+ primaryColor: rawBranding.primaryColor,
816
+ secondaryColor: rawBranding.secondaryColor,
817
+ verified: rawBranding.verified,
818
+ whiteLabelApproved: rawBranding.whiteLabelApproved
819
+ };
820
+ }
821
+ return result;
822
+ }
823
+ function extractErrorMessage(json, status) {
824
+ const error = json.error;
825
+ if (typeof error === "object" && error !== null) {
826
+ return error.message ?? `Embed API returned ${status}`;
827
+ }
828
+ if (typeof error === "string") {
829
+ return error;
830
+ }
831
+ return `Embed API returned ${status}`;
832
+ }
833
+ function extractErrorCode(json) {
834
+ const error = json.error;
835
+ if (typeof error === "object" && error !== null) {
836
+ return error.code ?? "embed_error";
837
+ }
838
+ return "embed_error";
839
+ }
840
+
589
841
  // src/jwt.ts
590
842
  var import_node_crypto3 = __toESM(require("crypto"));
591
843
  var DEFAULT_ISSUERS = [
@@ -740,7 +992,7 @@ async function verifyTokenJWKS(token, options) {
740
992
  }
741
993
 
742
994
  // src/graph.ts
743
- var DEFAULT_TIMEOUT_MS3 = 1e4;
995
+ var DEFAULT_TIMEOUT_MS4 = 1e4;
744
996
  var BIO_GRAPH_URL = "https://bio-graph.tawa.pro";
745
997
  var GraphClient = class _GraphClient {
746
998
  graphUrl;
@@ -749,7 +1001,7 @@ var GraphClient = class _GraphClient {
749
1001
  constructor(config = {}) {
750
1002
  this.graphUrl = (config.graphUrl ?? process.env.BIO_GRAPH_URL ?? BIO_GRAPH_URL).replace(/\/$/, "");
751
1003
  this.accessToken = config.accessToken;
752
- this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
1004
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
753
1005
  }
754
1006
  /**
755
1007
  * Create a GraphClient from environment variables.
@@ -902,6 +1154,7 @@ var GraphClient = class _GraphClient {
902
1154
  BioAdmin,
903
1155
  BioAuth,
904
1156
  BioError,
1157
+ EmbedClient,
905
1158
  GraphClient,
906
1159
  decodeToken,
907
1160
  generatePKCE,
package/dist/index.mjs CHANGED
@@ -515,6 +515,257 @@ var BioAdmin = class _BioAdmin {
515
515
  }
516
516
  };
517
517
 
518
+ // src/embed.ts
519
+ var DEFAULT_BIO_URL = "https://bio.tawa.pro";
520
+ var DEFAULT_TIMEOUT_MS3 = 1e4;
521
+ var EmbedClient = class _EmbedClient {
522
+ bioIdUrl;
523
+ clientId;
524
+ clientSecret;
525
+ retries;
526
+ timeoutMs;
527
+ constructor(config) {
528
+ if (!config.clientId) {
529
+ throw new BioError("clientId is required", "config_error");
530
+ }
531
+ if (!config.clientSecret) {
532
+ throw new BioError("clientSecret is required", "config_error");
533
+ }
534
+ this.clientId = config.clientId;
535
+ this.clientSecret = config.clientSecret;
536
+ this.bioIdUrl = (config.bioIdUrl ?? DEFAULT_BIO_URL).replace(/\/$/, "");
537
+ this.retries = config.retries ?? 2;
538
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
539
+ }
540
+ /**
541
+ * Create an EmbedClient from environment variables.
542
+ *
543
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
544
+ */
545
+ static fromEnv(overrides) {
546
+ const clientId = overrides?.clientId ?? process.env.BIO_CLIENT_ID;
547
+ const clientSecret = overrides?.clientSecret ?? process.env.BIO_CLIENT_SECRET;
548
+ if (!clientId) {
549
+ throw new BioError(
550
+ "BIO_CLIENT_ID environment variable is required",
551
+ "config_error"
552
+ );
553
+ }
554
+ if (!clientSecret) {
555
+ throw new BioError(
556
+ "BIO_CLIENT_SECRET environment variable is required",
557
+ "config_error"
558
+ );
559
+ }
560
+ return new _EmbedClient({
561
+ clientId,
562
+ clientSecret,
563
+ bioIdUrl: overrides?.bioIdUrl ?? process.env.BIO_ID_URL,
564
+ retries: overrides?.retries,
565
+ timeoutMs: overrides?.timeoutMs
566
+ });
567
+ }
568
+ /**
569
+ * Authenticate a user with email and password.
570
+ *
571
+ * @param params - Email and password
572
+ * @returns Access token, refresh token, user profile, and optional branding
573
+ */
574
+ async login(params) {
575
+ if (!params.email) throw new BioError("email is required", "validation_error");
576
+ if (!params.password) throw new BioError("password is required", "validation_error");
577
+ return this.embedRequest("/api/embed/login", {
578
+ email: params.email,
579
+ password: params.password
580
+ });
581
+ }
582
+ /**
583
+ * Create a new user account.
584
+ *
585
+ * @param params - Email, password, name, and optional invite token
586
+ * @returns Access token, refresh token, user profile, and optional branding
587
+ */
588
+ async signup(params) {
589
+ if (!params.email) throw new BioError("email is required", "validation_error");
590
+ if (!params.password) throw new BioError("password is required", "validation_error");
591
+ if (!params.name) throw new BioError("name is required", "validation_error");
592
+ const body = {
593
+ email: params.email,
594
+ password: params.password,
595
+ name: params.name
596
+ };
597
+ if (params.inviteToken) {
598
+ body.inviteToken = params.inviteToken;
599
+ }
600
+ return this.embedRequest("/api/embed/signup", body);
601
+ }
602
+ /**
603
+ * Send a magic link email to the user.
604
+ *
605
+ * The user clicks the link to authenticate without a password.
606
+ * After sending, use `verify()` with the token from the link.
607
+ *
608
+ * @param params - Email address to send the magic link to
609
+ */
610
+ async sendMagicLink(params) {
611
+ if (!params.email) throw new BioError("email is required", "validation_error");
612
+ const response = await this.fetchWithRetry(
613
+ "POST",
614
+ `${this.bioIdUrl}/api/embed/magic-link`,
615
+ JSON.stringify({ email: params.email })
616
+ );
617
+ const json = await parseJsonResponse(response);
618
+ if (!response.ok) {
619
+ throw new BioError(
620
+ extractErrorMessage(json, response.status),
621
+ extractErrorCode(json),
622
+ response.status,
623
+ json
624
+ );
625
+ }
626
+ }
627
+ /**
628
+ * Verify a magic link token and exchange it for auth tokens.
629
+ *
630
+ * @param params - The token from the magic link
631
+ * @returns Access token, refresh token, user profile, and optional branding
632
+ */
633
+ async verify(params) {
634
+ if (!params.token) throw new BioError("token is required", "validation_error");
635
+ return this.embedRequest("/api/embed/verify", {
636
+ token: params.token
637
+ });
638
+ }
639
+ /**
640
+ * Refresh an expired access token using a refresh token.
641
+ *
642
+ * @param params - The refresh token to exchange
643
+ * @returns New access token, rotated refresh token, user profile, and optional branding
644
+ */
645
+ async refresh(params) {
646
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
647
+ return this.embedRequest("/api/embed/refresh", {
648
+ refreshToken: params.refreshToken
649
+ });
650
+ }
651
+ /**
652
+ * Revoke a refresh token (logout).
653
+ *
654
+ * @param params - The refresh token to revoke
655
+ */
656
+ async logout(params) {
657
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
658
+ const response = await this.fetchWithRetry(
659
+ "POST",
660
+ `${this.bioIdUrl}/api/embed/logout`,
661
+ JSON.stringify({ refreshToken: params.refreshToken })
662
+ );
663
+ const json = await parseJsonResponse(response);
664
+ if (!response.ok) {
665
+ throw new BioError(
666
+ extractErrorMessage(json, response.status),
667
+ extractErrorCode(json),
668
+ response.status,
669
+ json
670
+ );
671
+ }
672
+ }
673
+ // ── Private helpers ──────────────────────────────────────────────────────
674
+ async embedRequest(path, body) {
675
+ const response = await this.fetchWithRetry(
676
+ "POST",
677
+ `${this.bioIdUrl}${path}`,
678
+ JSON.stringify(body)
679
+ );
680
+ const json = await parseJsonResponse(response);
681
+ if (!response.ok) {
682
+ throw new BioError(
683
+ extractErrorMessage(json, response.status),
684
+ extractErrorCode(json),
685
+ response.status,
686
+ json
687
+ );
688
+ }
689
+ return mapEmbedResponse(json);
690
+ }
691
+ async fetchWithRetry(method, url, body, attempt = 0) {
692
+ try {
693
+ const response = await fetch(url, {
694
+ method,
695
+ headers: {
696
+ "Content-Type": "application/json",
697
+ "X-Client-Id": this.clientId,
698
+ "X-Client-Secret": this.clientSecret
699
+ },
700
+ body,
701
+ signal: AbortSignal.timeout(this.timeoutMs)
702
+ });
703
+ if (response.status >= 500 && attempt < this.retries) {
704
+ await sleep(retryDelay(attempt));
705
+ return this.fetchWithRetry(method, url, body, attempt + 1);
706
+ }
707
+ return response;
708
+ } catch (err) {
709
+ if (attempt < this.retries) {
710
+ await sleep(retryDelay(attempt));
711
+ return this.fetchWithRetry(method, url, body, attempt + 1);
712
+ }
713
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
714
+ throw new BioError(
715
+ isTimeout ? `Request timed out after ${this.timeoutMs}ms` : err instanceof Error ? err.message : "Network error",
716
+ isTimeout ? "timeout" : "network_error"
717
+ );
718
+ }
719
+ }
720
+ };
721
+ function mapEmbedResponse(raw) {
722
+ const data = raw.data ?? raw;
723
+ const rawUser = data.user ?? {};
724
+ const rawBranding = data.branding;
725
+ const user = {
726
+ bioId: rawUser.bioId,
727
+ email: rawUser.email,
728
+ name: rawUser.name,
729
+ orgSlug: rawUser.orgSlug
730
+ };
731
+ const result = {
732
+ accessToken: data.access_token,
733
+ refreshToken: data.refresh_token,
734
+ tokenType: data.token_type ?? "Bearer",
735
+ expiresIn: data.expires_in,
736
+ user
737
+ };
738
+ if (rawBranding) {
739
+ result.branding = {
740
+ displayName: rawBranding.displayName,
741
+ logoUrl: rawBranding.logoUrl,
742
+ logoMarkUrl: rawBranding.logoMarkUrl,
743
+ primaryColor: rawBranding.primaryColor,
744
+ secondaryColor: rawBranding.secondaryColor,
745
+ verified: rawBranding.verified,
746
+ whiteLabelApproved: rawBranding.whiteLabelApproved
747
+ };
748
+ }
749
+ return result;
750
+ }
751
+ function extractErrorMessage(json, status) {
752
+ const error = json.error;
753
+ if (typeof error === "object" && error !== null) {
754
+ return error.message ?? `Embed API returned ${status}`;
755
+ }
756
+ if (typeof error === "string") {
757
+ return error;
758
+ }
759
+ return `Embed API returned ${status}`;
760
+ }
761
+ function extractErrorCode(json) {
762
+ const error = json.error;
763
+ if (typeof error === "object" && error !== null) {
764
+ return error.code ?? "embed_error";
765
+ }
766
+ return "embed_error";
767
+ }
768
+
518
769
  // src/jwt.ts
519
770
  import crypto3 from "crypto";
520
771
  var DEFAULT_ISSUERS = [
@@ -671,6 +922,7 @@ export {
671
922
  BioAdmin,
672
923
  BioAuth,
673
924
  BioError,
925
+ EmbedClient,
674
926
  GraphClient,
675
927
  decodeToken,
676
928
  generatePKCE,
@@ -298,5 +298,99 @@ interface AdminResponse<T> {
298
298
  limit: number;
299
299
  };
300
300
  }
301
+ /** Configuration for EmbedClient (headless embed auth) */
302
+ interface EmbedClientConfig {
303
+ /** OAuth client ID (env: BIO_CLIENT_ID) */
304
+ clientId: string;
305
+ /** OAuth client secret (env: BIO_CLIENT_SECRET) */
306
+ clientSecret: string;
307
+ /** Bio-ID base URL (env: BIO_ID_URL, default: https://bio.tawa.pro) */
308
+ bioIdUrl?: string;
309
+ /** Number of retry attempts on transient failures (default: 2) */
310
+ retries?: number;
311
+ /** Request timeout in milliseconds (default: 10000) */
312
+ timeoutMs?: number;
313
+ }
314
+ /** Parameters for embed login */
315
+ interface EmbedLoginParams {
316
+ /** User email address */
317
+ email: string;
318
+ /** User password */
319
+ password: string;
320
+ }
321
+ /** Parameters for embed signup */
322
+ interface EmbedSignupParams {
323
+ /** User email address */
324
+ email: string;
325
+ /** User password */
326
+ password: string;
327
+ /** Display name */
328
+ name: string;
329
+ /** Optional invite token for org-scoped signups */
330
+ inviteToken?: string;
331
+ }
332
+ /** Parameters for sending a magic link */
333
+ interface EmbedMagicLinkParams {
334
+ /** Email address to send the magic link to */
335
+ email: string;
336
+ }
337
+ /** Parameters for verifying a magic link token */
338
+ interface EmbedVerifyParams {
339
+ /** Magic link token from the email */
340
+ token: string;
341
+ }
342
+ /** Parameters for refreshing an access token */
343
+ interface EmbedRefreshParams {
344
+ /** Refresh token from a previous login/signup/verify/refresh */
345
+ refreshToken: string;
346
+ }
347
+ /** Parameters for logout (token revocation) */
348
+ interface EmbedLogoutParams {
349
+ /** Refresh token to revoke */
350
+ refreshToken: string;
351
+ }
352
+ /** User profile returned by embed endpoints */
353
+ interface EmbedUser {
354
+ /** Unique Bio-ID identifier */
355
+ bioId: string;
356
+ /** User email address */
357
+ email: string;
358
+ /** Display name */
359
+ name: string;
360
+ /** Organization slug (if the user belongs to an org) */
361
+ orgSlug?: string;
362
+ }
363
+ /** Branding data returned by embed endpoints */
364
+ interface EmbedBranding {
365
+ /** Organization display name */
366
+ displayName?: string;
367
+ /** Full logo URL */
368
+ logoUrl?: string;
369
+ /** Logo mark (icon) URL */
370
+ logoMarkUrl?: string;
371
+ /** Primary brand color (hex) */
372
+ primaryColor?: string;
373
+ /** Secondary brand color (hex) */
374
+ secondaryColor?: string;
375
+ /** Whether the org is verified */
376
+ verified?: boolean;
377
+ /** Whether white-label is approved for this org */
378
+ whiteLabelApproved?: boolean;
379
+ }
380
+ /** Auth result from embed login, signup, verify, or refresh */
381
+ interface EmbedAuthResult {
382
+ /** JWT access token */
383
+ accessToken: string;
384
+ /** Refresh token (use with refresh() or logout()) */
385
+ refreshToken: string;
386
+ /** Token type (always 'Bearer') */
387
+ tokenType: 'Bearer';
388
+ /** Access token lifetime in seconds */
389
+ expiresIn: number;
390
+ /** Authenticated user profile */
391
+ user: EmbedUser;
392
+ /** Optional org branding (present when the user belongs to a branded org) */
393
+ branding?: EmbedBranding;
394
+ }
301
395
 
302
- export type { AuthorizeOptions as A, BioAuthConfig as B, CreateDepartmentData as C, IntrospectResult as I, JWKSVerifyOptions as J, OrgMember as O, TokenResponse as T, UserFilters as U, VerifyOptions as V, AuthorizeResult as a, BioUser as b, BioAdminConfig as c, UpdateUserData as d, BioDepartment as e, BioRole as f, CreateRoleData as g, BioOAuthClient as h, CreateClientData as i, BioTokenPayload as j, AdminResponse as k, BioAddress as l, BioClientTokenPayload as m, BioMessaging as n, BioUsersConfig as o, OrgMemberFilters as p, OrgMembersResult as q };
396
+ export type { AuthorizeOptions as A, BioAuthConfig as B, CreateDepartmentData as C, EmbedClientConfig as E, IntrospectResult as I, JWKSVerifyOptions as J, OrgMember as O, TokenResponse as T, UserFilters as U, VerifyOptions as V, AuthorizeResult as a, BioUser as b, BioAdminConfig as c, UpdateUserData as d, BioDepartment as e, BioRole as f, CreateRoleData as g, BioOAuthClient as h, CreateClientData as i, EmbedLoginParams as j, EmbedAuthResult as k, EmbedSignupParams as l, EmbedMagicLinkParams as m, EmbedVerifyParams as n, EmbedRefreshParams as o, EmbedLogoutParams as p, BioTokenPayload as q, AdminResponse as r, BioAddress as s, BioClientTokenPayload as t, BioMessaging as u, BioUsersConfig as v, EmbedBranding as w, EmbedUser as x, OrgMemberFilters as y, OrgMembersResult as z };
@@ -298,5 +298,99 @@ interface AdminResponse<T> {
298
298
  limit: number;
299
299
  };
300
300
  }
301
+ /** Configuration for EmbedClient (headless embed auth) */
302
+ interface EmbedClientConfig {
303
+ /** OAuth client ID (env: BIO_CLIENT_ID) */
304
+ clientId: string;
305
+ /** OAuth client secret (env: BIO_CLIENT_SECRET) */
306
+ clientSecret: string;
307
+ /** Bio-ID base URL (env: BIO_ID_URL, default: https://bio.tawa.pro) */
308
+ bioIdUrl?: string;
309
+ /** Number of retry attempts on transient failures (default: 2) */
310
+ retries?: number;
311
+ /** Request timeout in milliseconds (default: 10000) */
312
+ timeoutMs?: number;
313
+ }
314
+ /** Parameters for embed login */
315
+ interface EmbedLoginParams {
316
+ /** User email address */
317
+ email: string;
318
+ /** User password */
319
+ password: string;
320
+ }
321
+ /** Parameters for embed signup */
322
+ interface EmbedSignupParams {
323
+ /** User email address */
324
+ email: string;
325
+ /** User password */
326
+ password: string;
327
+ /** Display name */
328
+ name: string;
329
+ /** Optional invite token for org-scoped signups */
330
+ inviteToken?: string;
331
+ }
332
+ /** Parameters for sending a magic link */
333
+ interface EmbedMagicLinkParams {
334
+ /** Email address to send the magic link to */
335
+ email: string;
336
+ }
337
+ /** Parameters for verifying a magic link token */
338
+ interface EmbedVerifyParams {
339
+ /** Magic link token from the email */
340
+ token: string;
341
+ }
342
+ /** Parameters for refreshing an access token */
343
+ interface EmbedRefreshParams {
344
+ /** Refresh token from a previous login/signup/verify/refresh */
345
+ refreshToken: string;
346
+ }
347
+ /** Parameters for logout (token revocation) */
348
+ interface EmbedLogoutParams {
349
+ /** Refresh token to revoke */
350
+ refreshToken: string;
351
+ }
352
+ /** User profile returned by embed endpoints */
353
+ interface EmbedUser {
354
+ /** Unique Bio-ID identifier */
355
+ bioId: string;
356
+ /** User email address */
357
+ email: string;
358
+ /** Display name */
359
+ name: string;
360
+ /** Organization slug (if the user belongs to an org) */
361
+ orgSlug?: string;
362
+ }
363
+ /** Branding data returned by embed endpoints */
364
+ interface EmbedBranding {
365
+ /** Organization display name */
366
+ displayName?: string;
367
+ /** Full logo URL */
368
+ logoUrl?: string;
369
+ /** Logo mark (icon) URL */
370
+ logoMarkUrl?: string;
371
+ /** Primary brand color (hex) */
372
+ primaryColor?: string;
373
+ /** Secondary brand color (hex) */
374
+ secondaryColor?: string;
375
+ /** Whether the org is verified */
376
+ verified?: boolean;
377
+ /** Whether white-label is approved for this org */
378
+ whiteLabelApproved?: boolean;
379
+ }
380
+ /** Auth result from embed login, signup, verify, or refresh */
381
+ interface EmbedAuthResult {
382
+ /** JWT access token */
383
+ accessToken: string;
384
+ /** Refresh token (use with refresh() or logout()) */
385
+ refreshToken: string;
386
+ /** Token type (always 'Bearer') */
387
+ tokenType: 'Bearer';
388
+ /** Access token lifetime in seconds */
389
+ expiresIn: number;
390
+ /** Authenticated user profile */
391
+ user: EmbedUser;
392
+ /** Optional org branding (present when the user belongs to a branded org) */
393
+ branding?: EmbedBranding;
394
+ }
301
395
 
302
- export type { AuthorizeOptions as A, BioAuthConfig as B, CreateDepartmentData as C, IntrospectResult as I, JWKSVerifyOptions as J, OrgMember as O, TokenResponse as T, UserFilters as U, VerifyOptions as V, AuthorizeResult as a, BioUser as b, BioAdminConfig as c, UpdateUserData as d, BioDepartment as e, BioRole as f, CreateRoleData as g, BioOAuthClient as h, CreateClientData as i, BioTokenPayload as j, AdminResponse as k, BioAddress as l, BioClientTokenPayload as m, BioMessaging as n, BioUsersConfig as o, OrgMemberFilters as p, OrgMembersResult as q };
396
+ export type { AuthorizeOptions as A, BioAuthConfig as B, CreateDepartmentData as C, EmbedClientConfig as E, IntrospectResult as I, JWKSVerifyOptions as J, OrgMember as O, TokenResponse as T, UserFilters as U, VerifyOptions as V, AuthorizeResult as a, BioUser as b, BioAdminConfig as c, UpdateUserData as d, BioDepartment as e, BioRole as f, CreateRoleData as g, BioOAuthClient as h, CreateClientData as i, EmbedLoginParams as j, EmbedAuthResult as k, EmbedSignupParams as l, EmbedMagicLinkParams as m, EmbedVerifyParams as n, EmbedRefreshParams as o, EmbedLogoutParams as p, BioTokenPayload as q, AdminResponse as r, BioAddress as s, BioClientTokenPayload as t, BioMessaging as u, BioUsersConfig as v, EmbedBranding as w, EmbedUser as x, OrgMemberFilters as y, OrgMembersResult as z };
package/dist/users.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as BioUsersConfig, p as OrgMemberFilters, q as OrgMembersResult } from './types-Dkb-drHZ.mjs';
1
+ import { v as BioUsersConfig, y as OrgMemberFilters, z as OrgMembersResult } from './types-CJe1FP61.mjs';
2
2
 
3
3
  /**
4
4
  * Client for Bio-ID user access endpoints.
package/dist/users.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as BioUsersConfig, p as OrgMemberFilters, q as OrgMembersResult } from './types-Dkb-drHZ.js';
1
+ import { v as BioUsersConfig, y as OrgMemberFilters, z as OrgMembersResult } from './types-CJe1FP61.js';
2
2
 
3
3
  /**
4
4
  * Client for Bio-ID user access endpoints.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insureco/bio",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "SDK for Bio-ID SSO integration on the Tawa platform",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",