@vunexa/lixa 0.1.6-alpha.13 → 0.1.6-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +392 -674
- package/dist/credentials/credentials-manager.d.ts +14 -0
- package/dist/credentials/credentials-manager.d.ts.map +1 -1
- package/dist/credentials/types.d.ts +29 -6
- package/dist/credentials/types.d.ts.map +1 -1
- package/dist/dao/session-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +24 -0
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/errors.d.ts +62 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +899 -88
- package/dist/identity/composite.d.ts +65 -0
- package/dist/identity/composite.d.ts.map +1 -0
- package/dist/identity/index.d.ts +4 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/self-managed.d.ts +66 -0
- package/dist/identity/self-managed.d.ts.map +1 -0
- package/dist/identity/types.d.ts +232 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/index.cjs +1543 -341
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +868 -80
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1461 -267
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +190 -35
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +12 -6
- package/dist/models/session.d.ts.map +1 -1
- package/dist/providers/IProvider.d.ts +4 -0
- package/dist/providers/IProvider.d.ts.map +1 -1
- package/dist/types.d.ts +140 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/user-info.d.ts.map +1 -1
- package/package.json +3 -11
package/dist/index.js
CHANGED
|
@@ -387,10 +387,65 @@ var WeakPasswordError = class extends LixaError {
|
|
|
387
387
|
}
|
|
388
388
|
};
|
|
389
389
|
var CredentialsNotConfiguredError = class extends LixaError {
|
|
390
|
-
constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} in LixaConfig to enable it.", details) {
|
|
390
|
+
constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} or identity: ... in LixaConfig to enable it.", details) {
|
|
391
391
|
super(message, "CREDENTIALS_NOT_CONFIGURED", details);
|
|
392
392
|
}
|
|
393
393
|
};
|
|
394
|
+
var IdentityProviderError = class extends LixaError {
|
|
395
|
+
provider;
|
|
396
|
+
statusCode;
|
|
397
|
+
constructor(message, provider, statusCode, details) {
|
|
398
|
+
super(message, "IDENTITY_PROVIDER_ERROR", { provider, statusCode, ...details });
|
|
399
|
+
this.provider = provider;
|
|
400
|
+
this.statusCode = statusCode;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var IdentityNotConfiguredError = class extends CredentialsNotConfiguredError {
|
|
404
|
+
constructor(message = "Identity provider is not configured in this Lixa instance. Pass identity: ... or credentials: {} in LixaConfig to enable it.", details) {
|
|
405
|
+
super(message, details);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
var AccountLinkingChallengeRequiredError = class extends LixaError {
|
|
409
|
+
challengeToken;
|
|
410
|
+
email;
|
|
411
|
+
incomingProvider;
|
|
412
|
+
incomingProviderUserId;
|
|
413
|
+
existingUserId;
|
|
414
|
+
existingProviders;
|
|
415
|
+
hasPassword;
|
|
416
|
+
constructor(message, details) {
|
|
417
|
+
super(message, "ACCOUNT_LINKING_CHALLENGE_REQUIRED", details);
|
|
418
|
+
this.challengeToken = details.challengeToken;
|
|
419
|
+
this.email = details.email;
|
|
420
|
+
this.incomingProvider = details.incomingProvider;
|
|
421
|
+
this.incomingProviderUserId = details.incomingProviderUserId;
|
|
422
|
+
this.existingUserId = details.existingUserId;
|
|
423
|
+
this.existingProviders = details.existingProviders;
|
|
424
|
+
this.hasPassword = details.hasPassword;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
var SSOAccountAlreadyExistsError = class extends LixaError {
|
|
428
|
+
existingProviders;
|
|
429
|
+
email;
|
|
430
|
+
constructor(email, existingProviders, details) {
|
|
431
|
+
super(
|
|
432
|
+
`An account with email '${email}' already exists via ${existingProviders.join(", ")}. Please sign in with your original provider and set a password in Settings.`,
|
|
433
|
+
"SSO_ACCOUNT_EXISTS",
|
|
434
|
+
{ email, existingProviders, ...details }
|
|
435
|
+
);
|
|
436
|
+
this.email = email;
|
|
437
|
+
this.existingProviders = existingProviders;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
var AccountNotVerifiedError = class extends LixaError {
|
|
441
|
+
constructor(identifier, details) {
|
|
442
|
+
super(
|
|
443
|
+
`Account${identifier ? ` '${identifier}'` : ""} has not been verified. Please confirm your account with the verification code sent to your email.`,
|
|
444
|
+
"ACCOUNT_NOT_VERIFIED",
|
|
445
|
+
{ identifier, ...details }
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
394
449
|
|
|
395
450
|
// src/credentials/credentials-manager.ts
|
|
396
451
|
var CredentialsManager = class {
|
|
@@ -554,6 +609,41 @@ var CredentialsManager = class {
|
|
|
554
609
|
await this.storage.updatePassword(user.id, newHash);
|
|
555
610
|
return true;
|
|
556
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Sets a password for a user who doesn't have one yet (e.g. SSO-first users),
|
|
614
|
+
* or updates an existing password without requiring the old one.
|
|
615
|
+
* This should only be called from an authenticated context (user already verified their identity via SSO).
|
|
616
|
+
*
|
|
617
|
+
* @param params - userId and newPassword
|
|
618
|
+
* @returns True if password was successfully set
|
|
619
|
+
*/
|
|
620
|
+
async setPassword(params) {
|
|
621
|
+
const policyResult = await validatePasswordPolicy(params.newPassword, this.policy);
|
|
622
|
+
if (!policyResult.valid) {
|
|
623
|
+
throw new WeakPasswordError(policyResult.errors);
|
|
624
|
+
}
|
|
625
|
+
const newHash = await this.hasher.hash(params.newPassword);
|
|
626
|
+
const existingUser = await this.storage.findUserById(params.userId);
|
|
627
|
+
if (existingUser) {
|
|
628
|
+
await this.storage.updatePassword(params.userId, newHash);
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
const now = Date.now();
|
|
632
|
+
const identifier = params.identifier || params.email || params.userId;
|
|
633
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
634
|
+
const isEmail = normalized.includes("@");
|
|
635
|
+
const user = {
|
|
636
|
+
id: params.userId,
|
|
637
|
+
identifier: normalized,
|
|
638
|
+
email: isEmail ? normalized : params.email?.trim().toLowerCase(),
|
|
639
|
+
username: !isEmail ? normalized : void 0,
|
|
640
|
+
passwordHash: newHash,
|
|
641
|
+
createdAt: now,
|
|
642
|
+
updatedAt: now
|
|
643
|
+
};
|
|
644
|
+
await this.storage.saveUser(user);
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
557
647
|
/**
|
|
558
648
|
* Finds a user by ID and returns safe user data.
|
|
559
649
|
*/
|
|
@@ -586,10 +676,388 @@ var CredentialsManager = class {
|
|
|
586
676
|
}
|
|
587
677
|
};
|
|
588
678
|
|
|
679
|
+
// src/identity/self-managed.ts
|
|
680
|
+
import crypto3 from "crypto";
|
|
681
|
+
var SelfManagedIdentityProvider = class {
|
|
682
|
+
name = "self-managed";
|
|
683
|
+
manager;
|
|
684
|
+
pendingSignups = /* @__PURE__ */ new Map();
|
|
685
|
+
requireVerification;
|
|
686
|
+
config;
|
|
687
|
+
constructor(configOrManager) {
|
|
688
|
+
if (configOrManager instanceof CredentialsManager) {
|
|
689
|
+
this.manager = configOrManager;
|
|
690
|
+
this.config = {};
|
|
691
|
+
this.requireVerification = false;
|
|
692
|
+
} else {
|
|
693
|
+
this.config = configOrManager || {};
|
|
694
|
+
this.manager = new CredentialsManager(this.config);
|
|
695
|
+
this.requireVerification = this.config.requireVerification ?? false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
mapUser(user) {
|
|
699
|
+
return {
|
|
700
|
+
id: user.id,
|
|
701
|
+
identifier: user.identifier,
|
|
702
|
+
username: user.username,
|
|
703
|
+
email: user.email,
|
|
704
|
+
emailVerified: true,
|
|
705
|
+
createdAt: user.createdAt,
|
|
706
|
+
updatedAt: user.updatedAt,
|
|
707
|
+
metadata: user.metadata,
|
|
708
|
+
provider: this.name
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
generateConfirmationCode() {
|
|
712
|
+
return String(crypto3.randomInt(1e5, 999999));
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Registers a new user with optional pending verification.
|
|
716
|
+
* When requireVerification is true, credentials are NOT persisted to the database until confirmSignUp() is called.
|
|
717
|
+
*/
|
|
718
|
+
async signUp(params) {
|
|
719
|
+
if (!this.requireVerification) {
|
|
720
|
+
const user = await this.manager.signUp(params);
|
|
721
|
+
return this.mapUser(user);
|
|
722
|
+
}
|
|
723
|
+
const rawIdentifier = params.username || params.identifier || params.email || "";
|
|
724
|
+
const trimmed = (rawIdentifier || "").trim();
|
|
725
|
+
if (!trimmed) {
|
|
726
|
+
throw new InvalidCredentialsError("Identifier cannot be empty");
|
|
727
|
+
}
|
|
728
|
+
const identifierType = this.config.identifierType || "both";
|
|
729
|
+
if (identifierType === "email") {
|
|
730
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
731
|
+
if (!emailRegex.test(trimmed)) {
|
|
732
|
+
throw new InvalidCredentialsError("Identifier must be a valid email address");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const normalized = trimmed.toLowerCase();
|
|
736
|
+
const isEmail = normalized.includes("@");
|
|
737
|
+
const email = params.email ? params.email.trim().toLowerCase() : isEmail ? normalized : void 0;
|
|
738
|
+
const username = params.username ? params.username.trim() : !isEmail ? normalized : void 0;
|
|
739
|
+
if (this.config.requireUsername && (!username || username.trim() === "")) {
|
|
740
|
+
throw new InvalidCredentialsError("Username is required");
|
|
741
|
+
}
|
|
742
|
+
if (this.config.requireEmail && (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))) {
|
|
743
|
+
throw new InvalidCredentialsError("A valid email address is required");
|
|
744
|
+
}
|
|
745
|
+
const existing = await this.manager.getUserByIdentifier(normalized);
|
|
746
|
+
if (existing) {
|
|
747
|
+
throw new UserAlreadyExistsError(normalized);
|
|
748
|
+
}
|
|
749
|
+
const existingPending = this.pendingSignups.get(normalized);
|
|
750
|
+
if (existingPending && existingPending.expiresAt > Date.now()) {
|
|
751
|
+
throw new UserAlreadyExistsError(normalized);
|
|
752
|
+
}
|
|
753
|
+
const policy = { minLength: 8, maxLength: 128, ...this.config.policy };
|
|
754
|
+
const policyResult = await validatePasswordPolicy(params.password, policy);
|
|
755
|
+
if (!policyResult.valid) {
|
|
756
|
+
throw new WeakPasswordError(policyResult.errors);
|
|
757
|
+
}
|
|
758
|
+
const passwordHash = await this.manager.getHasher().hash(params.password);
|
|
759
|
+
const userId = typeof crypto3.randomUUID === "function" ? crypto3.randomUUID() : crypto3.randomBytes(16).toString("hex");
|
|
760
|
+
const confirmationCode = this.generateConfirmationCode();
|
|
761
|
+
const now = Date.now();
|
|
762
|
+
const pending = {
|
|
763
|
+
identifier: normalized,
|
|
764
|
+
email,
|
|
765
|
+
username,
|
|
766
|
+
passwordHash,
|
|
767
|
+
confirmationCode,
|
|
768
|
+
userId,
|
|
769
|
+
metadata: params.metadata,
|
|
770
|
+
createdAt: now,
|
|
771
|
+
expiresAt: now + 15 * 60 * 1e3
|
|
772
|
+
// 15 minutes TTL
|
|
773
|
+
};
|
|
774
|
+
this.pendingSignups.set(normalized, pending);
|
|
775
|
+
console.log(`[Lixa Self-Managed] Verification code for '${normalized}': ${confirmationCode}`);
|
|
776
|
+
return {
|
|
777
|
+
id: userId,
|
|
778
|
+
identifier: normalized,
|
|
779
|
+
username,
|
|
780
|
+
email,
|
|
781
|
+
emailVerified: false,
|
|
782
|
+
createdAt: now,
|
|
783
|
+
updatedAt: now,
|
|
784
|
+
metadata: {
|
|
785
|
+
...params.metadata,
|
|
786
|
+
requiresVerification: true,
|
|
787
|
+
devConfirmationCode: confirmationCode
|
|
788
|
+
},
|
|
789
|
+
provider: this.name
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Confirms a pending signup registration using the verification code.
|
|
794
|
+
* Only after successful confirmation is the user persisted to the database.
|
|
795
|
+
*/
|
|
796
|
+
async confirmSignUp(params) {
|
|
797
|
+
const normalized = (params.identifier || "").trim().toLowerCase();
|
|
798
|
+
const pending = this.pendingSignups.get(normalized);
|
|
799
|
+
if (!pending) {
|
|
800
|
+
throw new InvalidCredentialsError("No pending registration found for this identifier. Please sign up first.");
|
|
801
|
+
}
|
|
802
|
+
if (pending.expiresAt < Date.now()) {
|
|
803
|
+
this.pendingSignups.delete(normalized);
|
|
804
|
+
throw new InvalidCredentialsError("Verification code has expired. Please sign up again.");
|
|
805
|
+
}
|
|
806
|
+
const code = (params.confirmationCode || "").trim();
|
|
807
|
+
if (code !== pending.confirmationCode) {
|
|
808
|
+
throw new InvalidCredentialsError("Invalid verification code.");
|
|
809
|
+
}
|
|
810
|
+
const storage = this.manager.getStorage();
|
|
811
|
+
const user = {
|
|
812
|
+
id: pending.userId,
|
|
813
|
+
identifier: pending.identifier,
|
|
814
|
+
email: pending.email,
|
|
815
|
+
username: pending.username,
|
|
816
|
+
passwordHash: pending.passwordHash,
|
|
817
|
+
createdAt: pending.createdAt,
|
|
818
|
+
updatedAt: Date.now(),
|
|
819
|
+
metadata: pending.metadata
|
|
820
|
+
};
|
|
821
|
+
await storage.saveUser(user);
|
|
822
|
+
this.pendingSignups.delete(normalized);
|
|
823
|
+
console.log(`[Lixa Self-Managed] User '${normalized}' verified and persisted to database.`);
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Resends the verification code for a pending signup.
|
|
828
|
+
*/
|
|
829
|
+
async resendConfirmationCode(params) {
|
|
830
|
+
const normalized = (params.identifier || "").trim().toLowerCase();
|
|
831
|
+
const pending = this.pendingSignups.get(normalized);
|
|
832
|
+
if (!pending) {
|
|
833
|
+
throw new InvalidCredentialsError("No pending registration found for this identifier.");
|
|
834
|
+
}
|
|
835
|
+
const newCode = this.generateConfirmationCode();
|
|
836
|
+
pending.confirmationCode = newCode;
|
|
837
|
+
pending.expiresAt = Date.now() + 15 * 60 * 1e3;
|
|
838
|
+
this.pendingSignups.set(normalized, pending);
|
|
839
|
+
console.log(`[Lixa Self-Managed] New verification code for '${normalized}': ${newCode}`);
|
|
840
|
+
return {
|
|
841
|
+
success: true,
|
|
842
|
+
deliveryMedium: "CONSOLE",
|
|
843
|
+
destination: pending.email || normalized
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Authenticates a user with password.
|
|
848
|
+
*/
|
|
849
|
+
async signIn(params) {
|
|
850
|
+
const user = await this.manager.verifyCredentials(params);
|
|
851
|
+
if (!user) {
|
|
852
|
+
throw new InvalidCredentialsError();
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
user: this.mapUser(user)
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Verifies credentials without throwing an error if invalid.
|
|
860
|
+
*/
|
|
861
|
+
async verifyCredentials(params) {
|
|
862
|
+
const user = await this.manager.verifyCredentials(params);
|
|
863
|
+
return user ? this.mapUser(user) : null;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Changes a user's password.
|
|
867
|
+
*/
|
|
868
|
+
async changePassword(params) {
|
|
869
|
+
return await this.manager.changePassword(params);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Retrieves a user by their unique ID.
|
|
873
|
+
*/
|
|
874
|
+
async getUserById(id) {
|
|
875
|
+
const user = await this.manager.getUserById(id);
|
|
876
|
+
return user ? this.mapUser(user) : null;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Retrieves a user by their identifier (email or username).
|
|
880
|
+
*/
|
|
881
|
+
async getUserByIdentifier(identifier) {
|
|
882
|
+
const user = await this.manager.getUserByIdentifier(identifier);
|
|
883
|
+
return user ? this.mapUser(user) : null;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Deletes a user from storage if supported by the storage adapter.
|
|
887
|
+
*/
|
|
888
|
+
async deleteUser(id) {
|
|
889
|
+
const storage = this.manager.getStorage();
|
|
890
|
+
if (typeof storage.deleteUser === "function") {
|
|
891
|
+
await storage.deleteUser(id);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Returns the underlying CredentialsManager instance.
|
|
896
|
+
*/
|
|
897
|
+
getCredentialsManager() {
|
|
898
|
+
return this.manager;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// src/identity/composite.ts
|
|
903
|
+
var CompositeIdentityProvider = class {
|
|
904
|
+
name = "composite";
|
|
905
|
+
providers;
|
|
906
|
+
defaultProvider;
|
|
907
|
+
oauthProviders;
|
|
908
|
+
constructor(options) {
|
|
909
|
+
if (!options.providers || Object.keys(options.providers).length === 0) {
|
|
910
|
+
throw new IdentityProviderError(
|
|
911
|
+
"CompositeIdentityProvider requires at least one child provider in 'providers'",
|
|
912
|
+
this.name
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
this.providers = options.providers;
|
|
916
|
+
this.defaultProvider = options.defaultProvider;
|
|
917
|
+
this.oauthProviders = options.oauthProviders;
|
|
918
|
+
if (this.defaultProvider && !this.providers[this.defaultProvider]) {
|
|
919
|
+
throw new IdentityProviderError(
|
|
920
|
+
`Configured defaultProvider '${String(this.defaultProvider)}' does not exist in 'providers'. Available: ${Object.keys(this.providers).join(", ")}`,
|
|
921
|
+
this.name
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Resolves the target child provider based on explicit parameter or defaultProvider.
|
|
927
|
+
* Throws an error immediately if the provider cannot be resolved or does not exist.
|
|
928
|
+
*/
|
|
929
|
+
selectProvider(providerName) {
|
|
930
|
+
const targetKey = providerName || this.defaultProvider;
|
|
931
|
+
if (!targetKey) {
|
|
932
|
+
throw new IdentityProviderError(
|
|
933
|
+
`No provider specified and no defaultProvider configured in CompositeIdentityProvider. Available providers: ${Object.keys(this.providers).join(", ")}`,
|
|
934
|
+
this.name
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const provider = this.providers[targetKey];
|
|
938
|
+
if (!provider) {
|
|
939
|
+
throw new IdentityProviderError(
|
|
940
|
+
`Provider '${targetKey}' is not configured in CompositeIdentityProvider. Available providers: ${Object.keys(this.providers).join(", ")}`,
|
|
941
|
+
this.name
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return provider;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Returns all OAuth / OIDC provider configurations aggregated across this composite provider
|
|
948
|
+
* and any child providers exposing `getOAuthProviders()` or `getProviders()`.
|
|
949
|
+
*/
|
|
950
|
+
getProviders() {
|
|
951
|
+
const result = {};
|
|
952
|
+
if (this.oauthProviders) {
|
|
953
|
+
Object.assign(result, this.oauthProviders);
|
|
954
|
+
}
|
|
955
|
+
for (const child of Object.values(this.providers)) {
|
|
956
|
+
if (typeof child.getOAuthProviders === "function") {
|
|
957
|
+
Object.assign(result, child.getOAuthProviders());
|
|
958
|
+
} else if (typeof child.getProviders === "function") {
|
|
959
|
+
Object.assign(result, child.getProviders());
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return result;
|
|
963
|
+
}
|
|
964
|
+
async signUp(params) {
|
|
965
|
+
const provider = this.selectProvider(params.provider);
|
|
966
|
+
return provider.signUp(params);
|
|
967
|
+
}
|
|
968
|
+
async signIn(params) {
|
|
969
|
+
const provider = this.selectProvider(params.provider);
|
|
970
|
+
return provider.signIn(params);
|
|
971
|
+
}
|
|
972
|
+
async verifyCredentials(params) {
|
|
973
|
+
const provider = this.selectProvider(params.provider);
|
|
974
|
+
if (typeof provider.verifyCredentials === "function") {
|
|
975
|
+
return provider.verifyCredentials(params);
|
|
976
|
+
}
|
|
977
|
+
const result = await provider.signIn(params);
|
|
978
|
+
return result.user;
|
|
979
|
+
}
|
|
980
|
+
async changePassword(params) {
|
|
981
|
+
const provider = this.selectProvider(params.provider);
|
|
982
|
+
if (typeof provider.changePassword !== "function") {
|
|
983
|
+
throw new IdentityProviderError(
|
|
984
|
+
`Password change is not supported by provider '${provider.name}'`,
|
|
985
|
+
this.name
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
return provider.changePassword(params);
|
|
989
|
+
}
|
|
990
|
+
async forgotPassword(params) {
|
|
991
|
+
const provider = this.selectProvider(params.provider);
|
|
992
|
+
if (typeof provider.forgotPassword !== "function") {
|
|
993
|
+
throw new IdentityProviderError(
|
|
994
|
+
`Password reset is not supported by provider '${provider.name}'`,
|
|
995
|
+
this.name
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return provider.forgotPassword(params);
|
|
999
|
+
}
|
|
1000
|
+
async confirmPasswordReset(params) {
|
|
1001
|
+
const provider = this.selectProvider(params.provider);
|
|
1002
|
+
if (typeof provider.confirmPasswordReset !== "function") {
|
|
1003
|
+
throw new IdentityProviderError(
|
|
1004
|
+
`Confirm password reset is not supported by provider '${provider.name}'`,
|
|
1005
|
+
this.name
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
return provider.confirmPasswordReset(params);
|
|
1009
|
+
}
|
|
1010
|
+
async confirmSignUp(params) {
|
|
1011
|
+
const provider = this.selectProvider(params.provider);
|
|
1012
|
+
if (typeof provider.confirmSignUp !== "function") {
|
|
1013
|
+
throw new IdentityProviderError(
|
|
1014
|
+
`Account confirmation is not supported by provider '${provider.name}'`,
|
|
1015
|
+
this.name
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
return provider.confirmSignUp(params);
|
|
1019
|
+
}
|
|
1020
|
+
async resendConfirmationCode(params) {
|
|
1021
|
+
const provider = this.selectProvider(params.provider);
|
|
1022
|
+
if (typeof provider.resendConfirmationCode !== "function") {
|
|
1023
|
+
throw new IdentityProviderError(
|
|
1024
|
+
`Resending confirmation code is not supported by provider '${provider.name}'`,
|
|
1025
|
+
this.name
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
return provider.resendConfirmationCode(params);
|
|
1029
|
+
}
|
|
1030
|
+
async getUserById(id, providerName) {
|
|
1031
|
+
const provider = this.selectProvider(providerName);
|
|
1032
|
+
if (typeof provider.getUserById !== "function") {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
return provider.getUserById(id);
|
|
1036
|
+
}
|
|
1037
|
+
async getUserByIdentifier(identifier, providerName) {
|
|
1038
|
+
const provider = this.selectProvider(providerName);
|
|
1039
|
+
if (typeof provider.getUserByIdentifier !== "function") {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
return provider.getUserByIdentifier(identifier);
|
|
1043
|
+
}
|
|
1044
|
+
async deleteUser(id, providerName) {
|
|
1045
|
+
const provider = this.selectProvider(providerName);
|
|
1046
|
+
if (typeof provider.deleteUser !== "function") {
|
|
1047
|
+
throw new IdentityProviderError(
|
|
1048
|
+
`User deletion is not supported by provider '${provider.name}'`,
|
|
1049
|
+
this.name
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
return provider.deleteUser(id);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
|
|
589
1056
|
// src/types.ts
|
|
590
1057
|
var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
|
|
591
1058
|
AccountLinkingStrategy2["AUTO_LINK_BY_VERIFIED_EMAIL"] = "AUTO_LINK_BY_VERIFIED_EMAIL";
|
|
592
1059
|
AccountLinkingStrategy2["ISOLATED"] = "ISOLATED";
|
|
1060
|
+
AccountLinkingStrategy2["STEP_UP_VERIFICATION"] = "STEP_UP_VERIFICATION";
|
|
593
1061
|
return AccountLinkingStrategy2;
|
|
594
1062
|
})(AccountLinkingStrategy || {});
|
|
595
1063
|
|
|
@@ -633,7 +1101,7 @@ var LocalStateHandler = class {
|
|
|
633
1101
|
};
|
|
634
1102
|
|
|
635
1103
|
// src/lixa.ts
|
|
636
|
-
import
|
|
1104
|
+
import crypto4 from "crypto";
|
|
637
1105
|
|
|
638
1106
|
// src/dao/session-cache.ts
|
|
639
1107
|
import NodeCache2 from "node-cache";
|
|
@@ -691,10 +1159,7 @@ var LocalSessionHandler = class {
|
|
|
691
1159
|
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
692
1160
|
throw new Error("No valid access token found in OAuth response");
|
|
693
1161
|
}
|
|
694
|
-
const session = {
|
|
695
|
-
token: tokenData.access_token,
|
|
696
|
-
raw: tokenData
|
|
697
|
-
};
|
|
1162
|
+
const session = {};
|
|
698
1163
|
return session;
|
|
699
1164
|
}
|
|
700
1165
|
// PascalCase alias for backward compatibility
|
|
@@ -755,7 +1220,7 @@ async function fetchUserInfo(accessToken, userInfoEndpoint) {
|
|
|
755
1220
|
family_name: "family_name" in data ? String(data.family_name) : void 0,
|
|
756
1221
|
name: "name" in data ? String(data.name) : void 0,
|
|
757
1222
|
picture: "picture" in data ? String(data.picture) : void 0,
|
|
758
|
-
email_verified: "email_verified" in data ?
|
|
1223
|
+
email_verified: "email_verified" in data ? data.email_verified === true || data.email_verified === "true" : void 0,
|
|
759
1224
|
iss: "iss" in data ? String(data.iss) : void 0
|
|
760
1225
|
};
|
|
761
1226
|
}
|
|
@@ -763,15 +1228,151 @@ async function extractUserInfo(tokenData, providerMetadata) {
|
|
|
763
1228
|
let userInfo;
|
|
764
1229
|
const userInfoEndpoint = providerMetadata.endpoints.userInfo;
|
|
765
1230
|
if (tokenData.id_token) {
|
|
766
|
-
|
|
767
|
-
|
|
1231
|
+
try {
|
|
1232
|
+
userInfo = decodeIdToken(tokenData.id_token);
|
|
1233
|
+
if (userInfo && userInfo.email_verified !== void 0) {
|
|
1234
|
+
userInfo.email_verified = userInfo.email_verified === true || userInfo.email_verified === "true";
|
|
1235
|
+
}
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if ((!userInfo || !userInfo.email) && tokenData.access_token && userInfoEndpoint) {
|
|
1240
|
+
try {
|
|
1241
|
+
const fetched = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
|
|
1242
|
+
userInfo = { ...fetched, ...userInfo || {}, email: fetched.email || userInfo?.email || "" };
|
|
1243
|
+
if (fetched.email_verified !== void 0 && (userInfo.email_verified === void 0 || userInfo.email_verified === false)) {
|
|
1244
|
+
userInfo.email_verified = fetched.email_verified === true || fetched.email_verified === "true";
|
|
1245
|
+
}
|
|
1246
|
+
} catch {
|
|
1247
|
+
}
|
|
1248
|
+
} else if (!userInfo && tokenData.access_token && userInfoEndpoint) {
|
|
768
1249
|
userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
|
|
769
|
-
}
|
|
1250
|
+
}
|
|
1251
|
+
if (!userInfo) {
|
|
770
1252
|
throw new Error("No ID token or access token available to fetch user info");
|
|
771
1253
|
}
|
|
772
1254
|
return { userInfo };
|
|
773
1255
|
}
|
|
774
1256
|
|
|
1257
|
+
// src/utils/cookies.ts
|
|
1258
|
+
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
1259
|
+
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
1260
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
1261
|
+
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
1262
|
+
function isProductionEnvironment() {
|
|
1263
|
+
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
1264
|
+
}
|
|
1265
|
+
function serializeCookie(name, value, options) {
|
|
1266
|
+
const isProd = isProductionEnvironment();
|
|
1267
|
+
const path = options?.path ?? "/";
|
|
1268
|
+
const httpOnly = options?.httpOnly ?? true;
|
|
1269
|
+
const secure = options?.secure ?? isProd;
|
|
1270
|
+
const sameSite = options?.sameSite ?? "lax";
|
|
1271
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
1272
|
+
if (path) {
|
|
1273
|
+
parts.push(`Path=${path}`);
|
|
1274
|
+
}
|
|
1275
|
+
if (typeof options?.maxAge === "number") {
|
|
1276
|
+
parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
|
|
1277
|
+
const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
|
|
1278
|
+
parts.push(`Expires=${expires}`);
|
|
1279
|
+
}
|
|
1280
|
+
if (options?.domain) {
|
|
1281
|
+
parts.push(`Domain=${options.domain}`);
|
|
1282
|
+
}
|
|
1283
|
+
if (httpOnly) {
|
|
1284
|
+
parts.push("HttpOnly");
|
|
1285
|
+
}
|
|
1286
|
+
if (secure) {
|
|
1287
|
+
parts.push("Secure");
|
|
1288
|
+
}
|
|
1289
|
+
if (sameSite) {
|
|
1290
|
+
const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
|
|
1291
|
+
parts.push(`SameSite=${capitalized}`);
|
|
1292
|
+
}
|
|
1293
|
+
return parts.join("; ");
|
|
1294
|
+
}
|
|
1295
|
+
function createSessionCookie(sessionId, options) {
|
|
1296
|
+
const isProd = isProductionEnvironment();
|
|
1297
|
+
const resolvedOptions = {
|
|
1298
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
1299
|
+
path: options?.path ?? "/",
|
|
1300
|
+
maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
1301
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1302
|
+
secure: options?.secure ?? isProd,
|
|
1303
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1304
|
+
domain: options?.domain
|
|
1305
|
+
};
|
|
1306
|
+
const name = resolvedOptions.name;
|
|
1307
|
+
const header = serializeCookie(name, sessionId, resolvedOptions);
|
|
1308
|
+
return {
|
|
1309
|
+
name,
|
|
1310
|
+
value: sessionId,
|
|
1311
|
+
options: resolvedOptions,
|
|
1312
|
+
header
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function clearSessionCookie(options) {
|
|
1316
|
+
const isProd = isProductionEnvironment();
|
|
1317
|
+
const resolvedOptions = {
|
|
1318
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
1319
|
+
path: options?.path ?? "/",
|
|
1320
|
+
maxAge: 0,
|
|
1321
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1322
|
+
secure: options?.secure ?? isProd,
|
|
1323
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1324
|
+
domain: options?.domain
|
|
1325
|
+
};
|
|
1326
|
+
const name = resolvedOptions.name;
|
|
1327
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
1328
|
+
return {
|
|
1329
|
+
name,
|
|
1330
|
+
value: "",
|
|
1331
|
+
options: resolvedOptions,
|
|
1332
|
+
header
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function createStateCookie(state, options) {
|
|
1336
|
+
const isProd = isProductionEnvironment();
|
|
1337
|
+
const resolvedOptions = {
|
|
1338
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
1339
|
+
path: options?.path ?? "/",
|
|
1340
|
+
maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
1341
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1342
|
+
secure: options?.secure ?? isProd,
|
|
1343
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1344
|
+
domain: options?.domain
|
|
1345
|
+
};
|
|
1346
|
+
const name = resolvedOptions.name;
|
|
1347
|
+
const header = serializeCookie(name, state, resolvedOptions);
|
|
1348
|
+
return {
|
|
1349
|
+
name,
|
|
1350
|
+
value: state,
|
|
1351
|
+
options: resolvedOptions,
|
|
1352
|
+
header
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
function clearStateCookie(options) {
|
|
1356
|
+
const isProd = isProductionEnvironment();
|
|
1357
|
+
const resolvedOptions = {
|
|
1358
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
1359
|
+
path: options?.path ?? "/",
|
|
1360
|
+
maxAge: 0,
|
|
1361
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1362
|
+
secure: options?.secure ?? isProd,
|
|
1363
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1364
|
+
domain: options?.domain
|
|
1365
|
+
};
|
|
1366
|
+
const name = resolvedOptions.name;
|
|
1367
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
1368
|
+
return {
|
|
1369
|
+
name,
|
|
1370
|
+
value: "",
|
|
1371
|
+
options: resolvedOptions,
|
|
1372
|
+
header
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
775
1376
|
// src/lixa.ts
|
|
776
1377
|
var Lixa = class _Lixa {
|
|
777
1378
|
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
@@ -781,14 +1382,19 @@ var Lixa = class _Lixa {
|
|
|
781
1382
|
localStateHandler;
|
|
782
1383
|
localSessionHandler;
|
|
783
1384
|
localResourceHandler;
|
|
1385
|
+
localAccountHandler;
|
|
784
1386
|
userResourceStore = /* @__PURE__ */ new Map();
|
|
1387
|
+
userAccountStore = /* @__PURE__ */ new Map();
|
|
785
1388
|
refreshMutexes = /* @__PURE__ */ new Map();
|
|
1389
|
+
identityProvider;
|
|
786
1390
|
credentialsManager;
|
|
787
1391
|
config;
|
|
788
1392
|
stateHandler;
|
|
789
1393
|
sessionHandler;
|
|
790
1394
|
resourceHandler;
|
|
1395
|
+
accountHandler;
|
|
791
1396
|
debug;
|
|
1397
|
+
sessionCookieResolver;
|
|
792
1398
|
/**
|
|
793
1399
|
* Creates a new Lixa instance with the provided configuration.
|
|
794
1400
|
*
|
|
@@ -803,7 +1409,11 @@ var Lixa = class _Lixa {
|
|
|
803
1409
|
* @throws Error when provider is not available and no inline implementation is provided
|
|
804
1410
|
*/
|
|
805
1411
|
constructor(config) {
|
|
806
|
-
this.
|
|
1412
|
+
this.sessionCookieResolver = config.sessionCookieName;
|
|
1413
|
+
this.config = {
|
|
1414
|
+
...config,
|
|
1415
|
+
...config.providers ? { providers: config.providers } : {}
|
|
1416
|
+
};
|
|
807
1417
|
this.debug = config.debug || false;
|
|
808
1418
|
this.localStateHandler = new LocalStateHandler();
|
|
809
1419
|
this.localSessionHandler = new LocalSessionHandler();
|
|
@@ -839,19 +1449,84 @@ var Lixa = class _Lixa {
|
|
|
839
1449
|
}
|
|
840
1450
|
}
|
|
841
1451
|
};
|
|
842
|
-
this.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1452
|
+
this.localAccountHandler = {
|
|
1453
|
+
accountStorage: {
|
|
1454
|
+
saveAccount: async (userId, provider, account) => {
|
|
1455
|
+
let userMap = this.userAccountStore.get(userId);
|
|
1456
|
+
if (!userMap) {
|
|
1457
|
+
userMap = /* @__PURE__ */ new Map();
|
|
1458
|
+
this.userAccountStore.set(userId, userMap);
|
|
1459
|
+
}
|
|
1460
|
+
userMap.set(provider.toLowerCase(), account);
|
|
1461
|
+
},
|
|
1462
|
+
getAccount: async (userId, provider) => {
|
|
1463
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1464
|
+
return userMap?.get(provider.toLowerCase()) || null;
|
|
1465
|
+
},
|
|
1466
|
+
getUserAccounts: async (userId) => {
|
|
1467
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1468
|
+
const result = {};
|
|
1469
|
+
if (userMap) {
|
|
1470
|
+
for (const [p, a] of userMap.entries()) {
|
|
1471
|
+
result[p] = a;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return result;
|
|
1475
|
+
},
|
|
1476
|
+
deleteAccount: async (userId, provider) => {
|
|
1477
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1478
|
+
if (userMap) {
|
|
1479
|
+
userMap.delete(provider.toLowerCase());
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
const storage = config.storage;
|
|
1485
|
+
this.stateHandler = config.stateHandler || (storage?.stateStorage ? { stateStorage: storage.stateStorage } : this.localStateHandler);
|
|
1486
|
+
this.sessionHandler = config.sessionHandler || (storage?.sessionStorage ? { sessionStorage: storage.sessionStorage } : this.localSessionHandler);
|
|
1487
|
+
this.resourceHandler = config.resourceHandler || (storage?.resourceStorage ? { resourceStorage: storage.resourceStorage } : this.localResourceHandler);
|
|
1488
|
+
this.accountHandler = storage?.accountStorage ? { accountStorage: storage.accountStorage } : this.localAccountHandler;
|
|
1489
|
+
const identityConfig = config.identityProvider || config.identity;
|
|
1490
|
+
if (identityConfig) {
|
|
1491
|
+
if (typeof identityConfig.signUp === "function") {
|
|
1492
|
+
this.identityProvider = identityConfig;
|
|
1493
|
+
if (this.identityProvider instanceof SelfManagedIdentityProvider) {
|
|
1494
|
+
this.credentialsManager = this.identityProvider.getCredentialsManager();
|
|
1495
|
+
} else if (this.identityProvider instanceof CompositeIdentityProvider) {
|
|
1496
|
+
for (const child of Object.values(this.identityProvider.providers)) {
|
|
1497
|
+
if (child instanceof SelfManagedIdentityProvider) {
|
|
1498
|
+
this.credentialsManager = child.getCredentialsManager();
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
} else {
|
|
1504
|
+
const selfManaged = new SelfManagedIdentityProvider(identityConfig);
|
|
1505
|
+
this.identityProvider = selfManaged;
|
|
1506
|
+
this.credentialsManager = selfManaged.getCredentialsManager();
|
|
1507
|
+
}
|
|
1508
|
+
} else if (config.credentials !== void 0 && config.credentials.enabled !== false) {
|
|
1509
|
+
const selfManaged = new SelfManagedIdentityProvider(config.credentials);
|
|
1510
|
+
this.identityProvider = selfManaged;
|
|
1511
|
+
this.credentialsManager = selfManaged.getCredentialsManager();
|
|
1512
|
+
}
|
|
1513
|
+
if (this.identityProvider && typeof this.identityProvider.getProviders === "function") {
|
|
1514
|
+
const idpOAuthProviders = this.identityProvider.getProviders();
|
|
1515
|
+
this.config.providers = {
|
|
1516
|
+
...idpOAuthProviders,
|
|
1517
|
+
...this.config.providers || {}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
const oauthProviders = this.config.providers;
|
|
848
1521
|
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
849
|
-
providers:
|
|
850
|
-
|
|
1522
|
+
providers: oauthProviders ? Object.keys(oauthProviders) : [],
|
|
1523
|
+
identityProvider: this.identityProvider ? this.identityProvider.name : void 0,
|
|
1524
|
+
identityEnabled: this.identityProvider !== void 0,
|
|
1525
|
+
credentialsEnabled: this.identityProvider !== void 0,
|
|
851
1526
|
debug: this.debug
|
|
852
1527
|
});
|
|
853
|
-
if (
|
|
854
|
-
for (const [providerName, providerConfig] of Object.entries(
|
|
1528
|
+
if (oauthProviders) {
|
|
1529
|
+
for (const [providerName, providerConfig] of Object.entries(oauthProviders)) {
|
|
855
1530
|
const name = providerName.toLowerCase();
|
|
856
1531
|
const typedConfig = providerConfig;
|
|
857
1532
|
this.validateProviderConfig(providerName, typedConfig);
|
|
@@ -880,7 +1555,22 @@ var Lixa = class _Lixa {
|
|
|
880
1555
|
* @throws InvalidProviderConfigError when required fields are missing or invalid
|
|
881
1556
|
*/
|
|
882
1557
|
validateProviderConfig(name, config) {
|
|
883
|
-
const
|
|
1558
|
+
const hasScopes = Array.isArray(config.scopes) && config.scopes.length > 0 || config.scopes && typeof config.scopes === "object" && !Array.isArray(config.scopes) && (config.scopes.auth && config.scopes.auth.length > 0 || config.scopes.resource && config.scopes.resource.length > 0) || config.authScopes && config.authScopes.length > 0 || config.resourceScopes && config.resourceScopes.length > 0;
|
|
1559
|
+
if (!hasScopes) {
|
|
1560
|
+
const p = config.provider;
|
|
1561
|
+
if (p?.defaultScopes && Array.isArray(p.defaultScopes) && p.defaultScopes.length > 0) {
|
|
1562
|
+
config.scopes = { auth: [...p.defaultScopes] };
|
|
1563
|
+
} else if (p?.authScopes && Array.isArray(p.authScopes) && p.authScopes.length > 0) {
|
|
1564
|
+
config.scopes = { auth: [...p.authScopes] };
|
|
1565
|
+
} else if (name === "google" || name.startsWith("google")) {
|
|
1566
|
+
config.scopes = { auth: ["openid", "profile", "email"] };
|
|
1567
|
+
} else if (name === "github" || name.startsWith("github")) {
|
|
1568
|
+
config.scopes = { auth: ["read:user", "user:email"] };
|
|
1569
|
+
} else if (name.startsWith("cognito")) {
|
|
1570
|
+
config.scopes = { auth: ["openid", "email", "profile"] };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
const requiredFields = ["clientId", "clientSecret", "redirectUri"];
|
|
884
1574
|
const missingFields = requiredFields.filter((field) => {
|
|
885
1575
|
const value = config[field];
|
|
886
1576
|
return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
@@ -891,16 +1581,33 @@ var Lixa = class _Lixa {
|
|
|
891
1581
|
{ provider: name, missingFields }
|
|
892
1582
|
);
|
|
893
1583
|
}
|
|
894
|
-
if (
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1584
|
+
if (config.scopes) {
|
|
1585
|
+
if (Array.isArray(config.scopes)) {
|
|
1586
|
+
if (config.scopes.length === 0) {
|
|
1587
|
+
throw new InvalidProviderConfigError(
|
|
1588
|
+
`Provider '${name}' configuration error: 'scopes' array cannot be empty`,
|
|
1589
|
+
{ provider: name }
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
} else if (typeof config.scopes === "object") {
|
|
1593
|
+
const hasAuth = Array.isArray(config.scopes.auth) && config.scopes.auth.length > 0;
|
|
1594
|
+
const hasResource = Array.isArray(config.scopes.resource) && config.scopes.resource.length > 0;
|
|
1595
|
+
if (!hasAuth && !hasResource) {
|
|
1596
|
+
throw new InvalidProviderConfigError(
|
|
1597
|
+
`Provider '${name}' configuration error: 'scopes' object must specify non-empty 'auth' or 'resource' array`,
|
|
1598
|
+
{ provider: name }
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
} else {
|
|
1602
|
+
throw new InvalidProviderConfigError(
|
|
1603
|
+
`Provider '${name}' configuration error: 'scopes' must be an array of strings or { auth?: string[]; resource?: string[] }`,
|
|
1604
|
+
{ provider: name }
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
} else if (!config.authScopes && !config.resourceScopes) {
|
|
901
1608
|
throw new InvalidProviderConfigError(
|
|
902
|
-
`Provider '${name}' configuration
|
|
903
|
-
{ provider: name }
|
|
1609
|
+
`Provider '${name}' configuration is missing required fields: scopes`,
|
|
1610
|
+
{ provider: name, missingFields: ["scopes"] }
|
|
904
1611
|
);
|
|
905
1612
|
}
|
|
906
1613
|
}
|
|
@@ -1156,7 +1863,7 @@ var Lixa = class _Lixa {
|
|
|
1156
1863
|
* @internal
|
|
1157
1864
|
*/
|
|
1158
1865
|
static buildCodeChallenge(codeVerifier) {
|
|
1159
|
-
const hash =
|
|
1866
|
+
const hash = crypto4.createHash("sha256").update(codeVerifier).digest("base64");
|
|
1160
1867
|
return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1161
1868
|
}
|
|
1162
1869
|
/**
|
|
@@ -1214,12 +1921,17 @@ var Lixa = class _Lixa {
|
|
|
1214
1921
|
}
|
|
1215
1922
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
1216
1923
|
this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
|
|
1924
|
+
const rawAuthScopes = providerConfig.authScopes || (Array.isArray(providerConfig.scopes) ? providerConfig.scopes : providerConfig.scopes?.auth) || [];
|
|
1217
1925
|
const authNScopes = this.resolveAuthNScopes(
|
|
1218
1926
|
providerType,
|
|
1219
|
-
|
|
1927
|
+
rawAuthScopes,
|
|
1220
1928
|
providerImpl,
|
|
1221
1929
|
providerConfig.allowNonAuthScopes
|
|
1222
1930
|
);
|
|
1931
|
+
const authorizationParams = {
|
|
1932
|
+
...providerConfig.extraConfig || {},
|
|
1933
|
+
...providerConfig.authorizationParams || {}
|
|
1934
|
+
};
|
|
1223
1935
|
const params = new URLSearchParams({
|
|
1224
1936
|
client_id: providerConfig.clientId,
|
|
1225
1937
|
redirect_uri: providerConfig.redirectUri,
|
|
@@ -1228,7 +1940,7 @@ var Lixa = class _Lixa {
|
|
|
1228
1940
|
response_type: "code",
|
|
1229
1941
|
code_challenge: codeChallenge,
|
|
1230
1942
|
code_challenge_method: "S256",
|
|
1231
|
-
...
|
|
1943
|
+
...authorizationParams
|
|
1232
1944
|
});
|
|
1233
1945
|
const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;
|
|
1234
1946
|
this.log("INFO", "Auth", `Authorization URL generated successfully`, {
|
|
@@ -1302,7 +2014,8 @@ var Lixa = class _Lixa {
|
|
|
1302
2014
|
async handleCallback({
|
|
1303
2015
|
provider,
|
|
1304
2016
|
code,
|
|
1305
|
-
state
|
|
2017
|
+
state,
|
|
2018
|
+
sessionId
|
|
1306
2019
|
}) {
|
|
1307
2020
|
const providerType = String(provider).toLowerCase();
|
|
1308
2021
|
this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
|
|
@@ -1366,53 +2079,156 @@ var Lixa = class _Lixa {
|
|
|
1366
2079
|
}
|
|
1367
2080
|
const session = await generateSession(tokens, providerMetadata);
|
|
1368
2081
|
session.provider = session.provider || providerType;
|
|
2082
|
+
session.token = session.token || tokens.access_token;
|
|
2083
|
+
session.raw = session.raw || tokens;
|
|
2084
|
+
if (extractedUserInfo?.sub && !session.userId) {
|
|
2085
|
+
session.userId = extractedUserInfo.sub;
|
|
2086
|
+
} else if (extractedUserInfo?.id && !session.userId) {
|
|
2087
|
+
session.userId = String(extractedUserInfo.id);
|
|
2088
|
+
}
|
|
1369
2089
|
if (extractedUserInfo?.email && !session.email) {
|
|
1370
2090
|
session.email = extractedUserInfo.email;
|
|
1371
2091
|
}
|
|
1372
2092
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1373
2093
|
const linkingConfig = this.config.accountLinking;
|
|
1374
|
-
const mode = String(linkingConfig?.mode || "");
|
|
1375
|
-
const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
|
|
2094
|
+
const mode = typeof linkingConfig === "object" ? String(linkingConfig?.mode || "") : "";
|
|
2095
|
+
const isLinkByEmail = linkingConfig === true || linkingConfig === "linkByEmail" || linkingConfig === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
|
|
2096
|
+
const isStepUpMode = linkingConfig === "STEP_UP_VERIFICATION" || linkingConfig === "STEP_UP_VERIFICATION" /* STEP_UP_VERIFICATION */ || mode === "STEP_UP_VERIFICATION" || mode === "STEP_UP_VERIFICATION" /* STEP_UP_VERIFICATION */ || mode === "stepUp";
|
|
1376
2097
|
const email = extractedUserInfo?.email;
|
|
1377
|
-
const isVerified = extractedUserInfo?.email_verified
|
|
1378
|
-
const requireVerified = linkingConfig?.requireVerifiedEmail ?? true;
|
|
1379
|
-
const canLink = isLinkByEmail && email && (!requireVerified || isVerified);
|
|
2098
|
+
const isVerified = extractedUserInfo?.email_verified === true;
|
|
2099
|
+
const requireVerified = typeof linkingConfig === "object" ? linkingConfig?.requireVerifiedEmail ?? true : true;
|
|
2100
|
+
const canLink = (isLinkByEmail || isStepUpMode) && email && (!requireVerified || isVerified);
|
|
2101
|
+
if ((isLinkByEmail || isStepUpMode) && email && !isVerified && requireVerified) {
|
|
2102
|
+
this.log("WARN", "AccountLinking", `Rejected account linking for provider '${providerType}': email '${email}' is not cryptographically verified by the provider.`);
|
|
2103
|
+
}
|
|
2104
|
+
if (sessionId) {
|
|
2105
|
+
const activeSession = await sessionStorage.getSession(sessionId);
|
|
2106
|
+
if (activeSession) {
|
|
2107
|
+
this.log("INFO", "AccountLinking", `Linking provider '${providerType}' directly to active session '${sessionId}'`);
|
|
2108
|
+
const linkedAcc2 = {
|
|
2109
|
+
provider: providerType,
|
|
2110
|
+
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
2111
|
+
email: extractedUserInfo?.email || activeSession.email,
|
|
2112
|
+
linkedAt: Date.now()
|
|
2113
|
+
};
|
|
2114
|
+
if (this.accountHandler?.accountStorage) {
|
|
2115
|
+
const userKey = this.getUserKeyFromSession(activeSession);
|
|
2116
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc2));
|
|
2117
|
+
}
|
|
2118
|
+
return sessionId;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
if (canLink) {
|
|
2122
|
+
const existingAccounts = this.accountHandler?.accountStorage?.findAccountsByEmail ? await this.accountHandler.accountStorage.findAccountsByEmail(email) : [];
|
|
2123
|
+
const existingSessionRecord = sessionStorage.getSessionByEmail ? await sessionStorage.getSessionByEmail(email) : null;
|
|
2124
|
+
const existingProviders = /* @__PURE__ */ new Set();
|
|
2125
|
+
let existingUserId;
|
|
2126
|
+
if (existingSessionRecord) {
|
|
2127
|
+
existingUserId = existingSessionRecord.session.userId || existingSessionRecord.session.email;
|
|
2128
|
+
if (existingSessionRecord.session.provider) {
|
|
2129
|
+
existingProviders.add(existingSessionRecord.session.provider.toLowerCase());
|
|
2130
|
+
}
|
|
2131
|
+
if (existingSessionRecord.session.accounts) {
|
|
2132
|
+
Object.keys(existingSessionRecord.session.accounts).forEach(
|
|
2133
|
+
(p) => existingProviders.add(p.toLowerCase())
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
for (const acc of existingAccounts) {
|
|
2138
|
+
if (!existingUserId) existingUserId = acc.userId;
|
|
2139
|
+
existingProviders.add(acc.provider.toLowerCase());
|
|
2140
|
+
}
|
|
2141
|
+
if (existingProviders.size > 0 && !existingProviders.has(providerType.toLowerCase())) {
|
|
2142
|
+
if (isStepUpMode) {
|
|
2143
|
+
let hasPassword = existingProviders.has("credentials") || existingProviders.has("self-managed") || existingProviders.has("cognito");
|
|
2144
|
+
if (!hasPassword && this.credentialsManager) {
|
|
2145
|
+
try {
|
|
2146
|
+
const credUser = await this.credentialsManager.getUserByIdentifier(email);
|
|
2147
|
+
if (credUser) {
|
|
2148
|
+
hasPassword = true;
|
|
2149
|
+
}
|
|
2150
|
+
} catch {
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const challengeToken = randomBytes2(24).toString("hex");
|
|
2154
|
+
const stateStorage2 = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
2155
|
+
await stateStorage2.saveState(
|
|
2156
|
+
`linking_challenge:${challengeToken}`,
|
|
2157
|
+
{
|
|
2158
|
+
provider: providerType,
|
|
2159
|
+
codeVerifier: "",
|
|
2160
|
+
createdAt: Date.now(),
|
|
2161
|
+
challengeToken,
|
|
2162
|
+
email,
|
|
2163
|
+
incomingProvider: providerType,
|
|
2164
|
+
incomingProviderUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
2165
|
+
existingUserId: existingUserId || email,
|
|
2166
|
+
existingProviders: Array.from(existingProviders),
|
|
2167
|
+
hasPassword,
|
|
2168
|
+
tokens
|
|
2169
|
+
},
|
|
2170
|
+
600
|
|
2171
|
+
);
|
|
2172
|
+
this.log(
|
|
2173
|
+
"WARN",
|
|
2174
|
+
"AccountLinking",
|
|
2175
|
+
`Step-up challenge required for user '${email}'. Existing: [${Array.from(existingProviders).join(
|
|
2176
|
+
", "
|
|
2177
|
+
)}], Incoming: '${providerType}'`
|
|
2178
|
+
);
|
|
2179
|
+
throw new AccountLinkingChallengeRequiredError(
|
|
2180
|
+
`An account with email '${email}' already exists. Verification required before linking.`,
|
|
2181
|
+
{
|
|
2182
|
+
challengeToken,
|
|
2183
|
+
email,
|
|
2184
|
+
incomingProvider: providerType,
|
|
2185
|
+
incomingProviderUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
2186
|
+
existingUserId: existingUserId || email,
|
|
2187
|
+
existingProviders: Array.from(existingProviders),
|
|
2188
|
+
hasPassword
|
|
2189
|
+
}
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
1380
2194
|
if (canLink && sessionStorage.getSessionByEmail) {
|
|
1381
2195
|
const existingRecord = await sessionStorage.getSessionByEmail(email);
|
|
1382
2196
|
if (existingRecord) {
|
|
1383
2197
|
this.log("INFO", "AccountLinking", `Linking provider '${providerType}' to existing session for email '${email}'`);
|
|
1384
2198
|
const { sessionId: existingSessionId, session: existingSession } = existingRecord;
|
|
1385
|
-
|
|
1386
|
-
existingSession.accounts[providerType] = {
|
|
2199
|
+
const linkedAcc2 = {
|
|
1387
2200
|
provider: providerType,
|
|
1388
2201
|
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1389
2202
|
email,
|
|
1390
|
-
accessToken: tokens.access_token,
|
|
1391
|
-
raw: tokens,
|
|
1392
2203
|
linkedAt: Date.now()
|
|
1393
2204
|
};
|
|
1394
2205
|
existingSession.provider = providerType;
|
|
1395
2206
|
existingSession.token = tokens.access_token;
|
|
1396
2207
|
existingSession.raw = tokens;
|
|
1397
2208
|
await sessionStorage.saveSession(existingSessionId, existingSession, 86400);
|
|
2209
|
+
if (this.accountHandler?.accountStorage) {
|
|
2210
|
+
const userKey = this.getUserKeyFromSession(existingSession);
|
|
2211
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc2));
|
|
2212
|
+
}
|
|
1398
2213
|
return existingSessionId;
|
|
1399
2214
|
}
|
|
1400
2215
|
}
|
|
1401
|
-
|
|
1402
|
-
session.accounts[providerType] = {
|
|
2216
|
+
const linkedAcc = {
|
|
1403
2217
|
provider: providerType,
|
|
1404
2218
|
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1405
2219
|
email: extractedUserInfo?.email,
|
|
1406
|
-
accessToken: tokens.access_token,
|
|
1407
|
-
raw: tokens,
|
|
1408
2220
|
linkedAt: Date.now()
|
|
1409
2221
|
};
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2222
|
+
if (this.accountHandler?.accountStorage) {
|
|
2223
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
2224
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc));
|
|
2225
|
+
}
|
|
2226
|
+
const newSessionId = randomBytes2(32).toString("hex");
|
|
2227
|
+
session.sessionId = newSessionId;
|
|
2228
|
+
this.log("INFO", "Session", "Storing session", { sessionId: newSessionId });
|
|
2229
|
+
await sessionStorage.saveSession(newSessionId, session, 86400);
|
|
2230
|
+
this.log("INFO", "Session", "Session created successfully", { sessionId: newSessionId });
|
|
2231
|
+
return newSessionId;
|
|
1416
2232
|
}
|
|
1417
2233
|
/**
|
|
1418
2234
|
* Explicitly link a new OAuth provider account to an active session.
|
|
@@ -1461,19 +2277,28 @@ var Lixa = class _Lixa {
|
|
|
1461
2277
|
extractedUserInfo = userInfo;
|
|
1462
2278
|
} catch {
|
|
1463
2279
|
}
|
|
1464
|
-
|
|
1465
|
-
existingSession.accounts[providerType] = {
|
|
2280
|
+
const linkedAcc = {
|
|
1466
2281
|
provider: providerType,
|
|
1467
2282
|
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1468
2283
|
email: extractedUserInfo?.email,
|
|
1469
|
-
accessToken: tokens.access_token,
|
|
1470
|
-
raw: tokens,
|
|
1471
2284
|
linkedAt: Date.now()
|
|
1472
2285
|
};
|
|
1473
2286
|
if (extractedUserInfo?.email && !existingSession.email) {
|
|
1474
2287
|
existingSession.email = extractedUserInfo.email;
|
|
2288
|
+
await sessionStorage.saveSession(sessionId, existingSession, 86400);
|
|
2289
|
+
}
|
|
2290
|
+
if (this.accountHandler?.accountStorage) {
|
|
2291
|
+
const userKey = this.getUserKeyFromSession(existingSession);
|
|
2292
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
2293
|
+
userKey,
|
|
2294
|
+
providerType,
|
|
2295
|
+
this.sanitizeIdentityForStorage(linkedAcc)
|
|
2296
|
+
);
|
|
1475
2297
|
}
|
|
1476
|
-
|
|
2298
|
+
this.log("INFO", "AccountLinking", `Linked provider '${providerType}' directly to session '${sessionId}'`, {
|
|
2299
|
+
provider: providerType,
|
|
2300
|
+
sessionId
|
|
2301
|
+
});
|
|
1477
2302
|
return sessionId;
|
|
1478
2303
|
}
|
|
1479
2304
|
/**
|
|
@@ -1486,19 +2311,171 @@ var Lixa = class _Lixa {
|
|
|
1486
2311
|
async unlinkAccount(sessionId, providerToUnlink) {
|
|
1487
2312
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1488
2313
|
const session = await sessionStorage.getSession(sessionId);
|
|
1489
|
-
if (!session
|
|
1490
|
-
throw new AccountUnlinkError("Session not found
|
|
2314
|
+
if (!session) {
|
|
2315
|
+
throw new AccountUnlinkError("Session not found.", { sessionId });
|
|
2316
|
+
}
|
|
2317
|
+
const providerKey = providerToUnlink.toLowerCase();
|
|
2318
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
2319
|
+
let allAccounts = {};
|
|
2320
|
+
if (this.accountHandler?.accountStorage) {
|
|
2321
|
+
allAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey) || {};
|
|
2322
|
+
}
|
|
2323
|
+
if (!allAccounts[providerKey]) {
|
|
2324
|
+
throw new AccountUnlinkError(`Provider '${providerToUnlink}' is not linked to this account.`, {
|
|
2325
|
+
sessionId,
|
|
2326
|
+
provider: providerToUnlink
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
const linkedProviders = Object.keys(allAccounts);
|
|
2330
|
+
if (linkedProviders.length <= 1) {
|
|
2331
|
+
throw new AccountUnlinkError("Cannot unlink the only authentication provider for this account.", {
|
|
2332
|
+
sessionId,
|
|
2333
|
+
provider: providerToUnlink
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
if (this.accountHandler?.accountStorage) {
|
|
2337
|
+
await this.accountHandler.accountStorage.deleteAccount(userKey, providerKey);
|
|
2338
|
+
}
|
|
2339
|
+
if (session.provider?.toLowerCase() === providerKey) {
|
|
2340
|
+
const remaining = linkedProviders.filter((p) => p !== providerKey);
|
|
2341
|
+
session.provider = remaining.length > 0 ? remaining[0] : void 0;
|
|
2342
|
+
await sessionStorage.saveSession(sessionId, session, 86400);
|
|
2343
|
+
}
|
|
2344
|
+
this.log("INFO", "AccountLinking", `Unlinked provider '${providerKey}' for user '${session.userId || session.email}'`, {
|
|
2345
|
+
sessionId,
|
|
2346
|
+
provider: providerKey
|
|
2347
|
+
});
|
|
2348
|
+
return true;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Confirms a pending account linking step-up challenge using the user's password.
|
|
2352
|
+
* On successful verification, links the pending provider to the existing account and creates a session.
|
|
2353
|
+
*
|
|
2354
|
+
* @param params - challengeToken and password
|
|
2355
|
+
* @returns Session ID, Session, and User identity
|
|
2356
|
+
* @throws InvalidStateError if challenge is invalid or expired
|
|
2357
|
+
* @throws InvalidCredentialsError if password verification fails
|
|
2358
|
+
*/
|
|
2359
|
+
async confirmLinkingWithPassword(params) {
|
|
2360
|
+
const { challengeToken, password } = params;
|
|
2361
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
2362
|
+
const stateKey = `linking_challenge:${challengeToken}`;
|
|
2363
|
+
const cached = await stateStorage.getState(stateKey);
|
|
2364
|
+
if (!cached || !cached.challengeToken) {
|
|
2365
|
+
throw new InvalidStateError("Invalid or expired account linking challenge.");
|
|
2366
|
+
}
|
|
2367
|
+
let verifiedUser = null;
|
|
2368
|
+
let passwordTokens = void 0;
|
|
2369
|
+
if (this.identityProvider) {
|
|
2370
|
+
try {
|
|
2371
|
+
const signInResult = await this.identityProvider.signIn({
|
|
2372
|
+
identifier: cached.email,
|
|
2373
|
+
password
|
|
2374
|
+
});
|
|
2375
|
+
verifiedUser = signInResult.user;
|
|
2376
|
+
passwordTokens = signInResult.tokens;
|
|
2377
|
+
} catch {
|
|
2378
|
+
verifiedUser = await this.verifyCredentials({
|
|
2379
|
+
identifier: cached.email,
|
|
2380
|
+
password
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
} else {
|
|
2384
|
+
verifiedUser = await this.verifyCredentials({
|
|
2385
|
+
identifier: cached.email,
|
|
2386
|
+
password
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
if (!verifiedUser) {
|
|
2390
|
+
throw new InvalidCredentialsError("Invalid password for account linking verification.");
|
|
2391
|
+
}
|
|
2392
|
+
await stateStorage.deleteState(stateKey);
|
|
2393
|
+
const providerType = String(cached.incomingProvider).toLowerCase();
|
|
2394
|
+
const incomingTokens = cached.tokens;
|
|
2395
|
+
const linkedAcc = {
|
|
2396
|
+
provider: providerType,
|
|
2397
|
+
providerUserId: cached.incomingProviderUserId,
|
|
2398
|
+
email: cached.email,
|
|
2399
|
+
linkedAt: Date.now()
|
|
2400
|
+
};
|
|
2401
|
+
if (this.accountHandler?.accountStorage) {
|
|
2402
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
2403
|
+
cached.existingUserId,
|
|
2404
|
+
providerType,
|
|
2405
|
+
this.sanitizeIdentityForStorage(linkedAcc)
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
const passwordProviderName = verifiedUser && verifiedUser.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider?.name) || "credentials";
|
|
2409
|
+
const passwordAccountKey = passwordProviderName === "self-managed" ? "credentials" : passwordProviderName;
|
|
2410
|
+
if (this.accountHandler?.accountStorage) {
|
|
2411
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
2412
|
+
cached.existingUserId,
|
|
2413
|
+
passwordAccountKey,
|
|
2414
|
+
this.sanitizeIdentityForStorage({
|
|
2415
|
+
provider: passwordAccountKey,
|
|
2416
|
+
providerUserId: verifiedUser.id,
|
|
2417
|
+
email: verifiedUser.email,
|
|
2418
|
+
linkedAt: Date.now()
|
|
2419
|
+
})
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
const sessionId = randomBytes2(32).toString("hex");
|
|
2423
|
+
const session = {
|
|
2424
|
+
sessionId,
|
|
2425
|
+
userId: cached.existingUserId,
|
|
2426
|
+
email: cached.email,
|
|
2427
|
+
provider: providerType,
|
|
2428
|
+
token: incomingTokens?.access_token,
|
|
2429
|
+
raw: incomingTokens
|
|
2430
|
+
};
|
|
2431
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
2432
|
+
await sessionStorage.saveSession(sessionId, session, 86400);
|
|
2433
|
+
this.log(
|
|
2434
|
+
"INFO",
|
|
2435
|
+
"AccountLinking",
|
|
2436
|
+
`Successfully linked '${providerType}' to user '${cached.existingUserId}' via password step-up.`
|
|
2437
|
+
);
|
|
2438
|
+
return { sessionId, session, user: verifiedUser };
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Sets a password for an SSO-authenticated user who doesn't have one yet.
|
|
2442
|
+
* Must be called from an authenticated context (user has active session proving identity).
|
|
2443
|
+
*
|
|
2444
|
+
* @param params - userId, newPassword, optional identifier and email
|
|
2445
|
+
* @returns True if password was set successfully
|
|
2446
|
+
* @throws IdentityNotConfiguredError if no identity provider configured
|
|
2447
|
+
* @throws WeakPasswordError if password doesn't meet policy
|
|
2448
|
+
*/
|
|
2449
|
+
async setPassword(params) {
|
|
2450
|
+
this.log("INFO", "Identity", "Processing setPassword request", { userId: params.userId });
|
|
2451
|
+
if (this.credentialsManager) {
|
|
2452
|
+
const result = await this.credentialsManager.setPassword(params);
|
|
2453
|
+
if (result && this.accountHandler?.accountStorage) {
|
|
2454
|
+
const providerName = this.identityProvider?.name === "self-managed" ? "credentials" : this.identityProvider?.name || "credentials";
|
|
2455
|
+
const credentialsAccount = {
|
|
2456
|
+
provider: providerName,
|
|
2457
|
+
providerUserId: params.userId,
|
|
2458
|
+
email: params.email,
|
|
2459
|
+
linkedAt: Date.now()
|
|
2460
|
+
};
|
|
2461
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
2462
|
+
params.userId,
|
|
2463
|
+
providerName,
|
|
2464
|
+
this.sanitizeIdentityForStorage(credentialsAccount)
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
this.log("INFO", "Identity", "Password set successfully", { userId: params.userId });
|
|
2468
|
+
return result;
|
|
1491
2469
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
});
|
|
2470
|
+
if (this.identityProvider && typeof this.identityProvider.changePassword === "function") {
|
|
2471
|
+
throw new IdentityProviderError(
|
|
2472
|
+
"Setting a password for SSO users is only supported with self-managed credentials. Use the provider's admin console instead.",
|
|
2473
|
+
this.identityProvider.name
|
|
2474
|
+
);
|
|
1498
2475
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
2476
|
+
throw new CredentialsNotConfiguredError(
|
|
2477
|
+
"Cannot set password: No credentials manager is configured on this Lixa instance."
|
|
2478
|
+
);
|
|
1502
2479
|
}
|
|
1503
2480
|
/**
|
|
1504
2481
|
* Generates an authorization URL for connecting a resource provider (AuthZ) post-login.
|
|
@@ -1512,7 +2489,7 @@ var Lixa = class _Lixa {
|
|
|
1512
2489
|
* @returns The authorization URL for resource consent
|
|
1513
2490
|
*/
|
|
1514
2491
|
async getResourceAuthUrl(params) {
|
|
1515
|
-
const { sessionId, provider,
|
|
2492
|
+
const { sessionId, provider, state, prompt, extraConfig, authorizationParams } = params;
|
|
1516
2493
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1517
2494
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1518
2495
|
if (!activeSession) {
|
|
@@ -1523,6 +2500,11 @@ var Lixa = class _Lixa {
|
|
|
1523
2500
|
if (!providerConfig) {
|
|
1524
2501
|
throw new ProviderNotConfiguredError(String(provider));
|
|
1525
2502
|
}
|
|
2503
|
+
const defaultResourceScopes = providerConfig.resourceScopes || (Array.isArray(providerConfig.scopes) ? void 0 : providerConfig.scopes?.resource) || [];
|
|
2504
|
+
const requestedScopes = params.scopes && params.scopes.length > 0 ? params.scopes : defaultResourceScopes;
|
|
2505
|
+
if (requestedScopes.length === 0) {
|
|
2506
|
+
throw new Error(`No scopes specified for resource '${provider}'. Provide scopes in getResourceAuthUrl() or configure 'scopes.resource' / 'resourceScopes' on the provider.`);
|
|
2507
|
+
}
|
|
1526
2508
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
1527
2509
|
const stateValue = state || randomBytes2(16).toString("hex");
|
|
1528
2510
|
const codeVerifier = randomBytes2(32).toString("hex");
|
|
@@ -1537,22 +2519,35 @@ var Lixa = class _Lixa {
|
|
|
1537
2519
|
300
|
|
1538
2520
|
);
|
|
1539
2521
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
2522
|
+
const mergedAuthParams = {
|
|
2523
|
+
...providerConfig.extraConfig || {},
|
|
2524
|
+
...providerConfig.authorizationParams || {},
|
|
2525
|
+
...extraConfig || {},
|
|
2526
|
+
...authorizationParams || {}
|
|
2527
|
+
};
|
|
1540
2528
|
const searchParams = new URLSearchParams({
|
|
1541
2529
|
client_id: providerConfig.clientId,
|
|
1542
2530
|
redirect_uri: providerConfig.redirectUri,
|
|
1543
|
-
scope:
|
|
2531
|
+
scope: requestedScopes.join(" "),
|
|
1544
2532
|
state: stateValue,
|
|
1545
2533
|
response_type: "code",
|
|
1546
2534
|
code_challenge: codeChallenge,
|
|
1547
2535
|
code_challenge_method: "S256",
|
|
1548
2536
|
...prompt ? { prompt } : {},
|
|
1549
|
-
...
|
|
1550
|
-
...extraConfig
|
|
2537
|
+
...mergedAuthParams
|
|
1551
2538
|
});
|
|
1552
2539
|
return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
|
|
1553
2540
|
}
|
|
1554
2541
|
getUserKeyFromSession(session) {
|
|
1555
|
-
return session.userId || session.email || session.
|
|
2542
|
+
return session.userId || session.email || session.sessionId || "anonymous";
|
|
2543
|
+
}
|
|
2544
|
+
sanitizeIdentityForStorage(account) {
|
|
2545
|
+
return {
|
|
2546
|
+
provider: account.provider,
|
|
2547
|
+
providerUserId: account.providerUserId,
|
|
2548
|
+
email: account.email,
|
|
2549
|
+
linkedAt: account.linkedAt || Date.now()
|
|
2550
|
+
};
|
|
1556
2551
|
}
|
|
1557
2552
|
/**
|
|
1558
2553
|
* Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
|
|
@@ -1582,12 +2577,14 @@ var Lixa = class _Lixa {
|
|
|
1582
2577
|
}
|
|
1583
2578
|
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
|
|
1584
2579
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
2580
|
+
const defaultResourceScopes = providerConfig.resourceScopes || (Array.isArray(providerConfig.scopes) ? void 0 : providerConfig.scopes?.resource) || [];
|
|
2581
|
+
const effectiveScopes = scopes || (tokens.scope ? tokens.scope.split(" ") : defaultResourceScopes);
|
|
1585
2582
|
const connectedResource = {
|
|
1586
2583
|
provider: providerType,
|
|
1587
2584
|
accessToken: tokens.access_token,
|
|
1588
2585
|
refreshToken: tokens.refresh_token,
|
|
1589
2586
|
expiresAt,
|
|
1590
|
-
scopes:
|
|
2587
|
+
scopes: effectiveScopes,
|
|
1591
2588
|
raw: tokens,
|
|
1592
2589
|
connectedAt: Date.now()
|
|
1593
2590
|
};
|
|
@@ -1599,6 +2596,45 @@ var Lixa = class _Lixa {
|
|
|
1599
2596
|
await sessionStorage.saveSession(sessionId, activeSession, 86400);
|
|
1600
2597
|
return activeSession;
|
|
1601
2598
|
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Retrieves configured default resource scopes for a provider.
|
|
2601
|
+
*
|
|
2602
|
+
* @param provider - Provider name (e.g. 'github', 'google')
|
|
2603
|
+
* @returns Array of configured resource scopes, or undefined if not configured
|
|
2604
|
+
*/
|
|
2605
|
+
getResourceScopes(provider) {
|
|
2606
|
+
const providerConfig = this.findProviderByType(String(provider).toLowerCase());
|
|
2607
|
+
if (!providerConfig) return void 0;
|
|
2608
|
+
if (providerConfig.resourceScopes) return providerConfig.resourceScopes;
|
|
2609
|
+
if (providerConfig.scopes && !Array.isArray(providerConfig.scopes)) {
|
|
2610
|
+
return providerConfig.scopes.resource;
|
|
2611
|
+
}
|
|
2612
|
+
return void 0;
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Returns the resolved session cookie name.
|
|
2616
|
+
* Dynamically reflects any changes to the active identity provider.
|
|
2617
|
+
*
|
|
2618
|
+
* @param req - Optional request object for multi-tenant request-scoped cookie resolution
|
|
2619
|
+
* @returns Resolved session cookie name
|
|
2620
|
+
*/
|
|
2621
|
+
getSessionCookieName(req) {
|
|
2622
|
+
if (typeof this.sessionCookieResolver === "function") {
|
|
2623
|
+
return this.sessionCookieResolver(this, req);
|
|
2624
|
+
}
|
|
2625
|
+
if (typeof this.sessionCookieResolver === "string" && this.sessionCookieResolver.length > 0) {
|
|
2626
|
+
return this.sessionCookieResolver;
|
|
2627
|
+
}
|
|
2628
|
+
return DEFAULT_SESSION_COOKIE_NAME;
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Sets or updates the session cookie name or dynamic resolver on this instance.
|
|
2632
|
+
*
|
|
2633
|
+
* @param resolver - Static cookie name or dynamic resolver function
|
|
2634
|
+
*/
|
|
2635
|
+
setSessionCookieName(resolver) {
|
|
2636
|
+
this.sessionCookieResolver = resolver;
|
|
2637
|
+
}
|
|
1602
2638
|
/**
|
|
1603
2639
|
* Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
|
|
1604
2640
|
* Automatically refreshes expired access tokens if a refresh token is present.
|
|
@@ -1779,7 +2815,32 @@ var Lixa = class _Lixa {
|
|
|
1779
2815
|
*/
|
|
1780
2816
|
async fetchSessionInfo(sessionId) {
|
|
1781
2817
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1782
|
-
|
|
2818
|
+
const session = await sessionStorage.getSession(sessionId);
|
|
2819
|
+
if (!session) return null;
|
|
2820
|
+
if (session.accounts) {
|
|
2821
|
+
delete session.accounts;
|
|
2822
|
+
}
|
|
2823
|
+
return session;
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Retrieves all linked accounts for a user from account storage.
|
|
2827
|
+
*
|
|
2828
|
+
* @param userIdOrSessionId - User ID, email, or active session ID
|
|
2829
|
+
* @returns Record mapping provider name to linked account metadata
|
|
2830
|
+
*/
|
|
2831
|
+
async getUserAccounts(userIdOrSessionId) {
|
|
2832
|
+
let userKey = userIdOrSessionId;
|
|
2833
|
+
if (this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage) {
|
|
2834
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
2835
|
+
const session = await sessionStorage.getSession(userIdOrSessionId);
|
|
2836
|
+
if (session) {
|
|
2837
|
+
userKey = this.getUserKeyFromSession(session);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
if (this.accountHandler?.accountStorage) {
|
|
2841
|
+
return await this.accountHandler.accountStorage.getUserAccounts(userKey) || {};
|
|
2842
|
+
}
|
|
2843
|
+
return {};
|
|
1783
2844
|
}
|
|
1784
2845
|
/**
|
|
1785
2846
|
* Deletes a session from session storage (e.g. on logout).
|
|
@@ -1830,36 +2891,91 @@ var Lixa = class _Lixa {
|
|
|
1830
2891
|
return response.json();
|
|
1831
2892
|
}
|
|
1832
2893
|
/**
|
|
1833
|
-
* Checks if
|
|
2894
|
+
* Checks if an identity provider (Self-Managed Database, AWS Cognito, Auth0, Okta, etc.) is configured.
|
|
2895
|
+
*
|
|
2896
|
+
* @returns True if identity provider is configured and available
|
|
2897
|
+
*/
|
|
2898
|
+
isIdentityEnabled() {
|
|
2899
|
+
return this.identityProvider !== void 0;
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Checks if credentials / identity authentication is configured and enabled.
|
|
1834
2903
|
*
|
|
1835
|
-
* @returns True if credentials authentication is available
|
|
2904
|
+
* @returns True if credentials / identity authentication is available
|
|
1836
2905
|
*/
|
|
1837
2906
|
isCredentialsEnabled() {
|
|
1838
|
-
return this.
|
|
2907
|
+
return this.identityProvider !== void 0;
|
|
1839
2908
|
}
|
|
1840
2909
|
/**
|
|
1841
|
-
* Returns the
|
|
2910
|
+
* Returns the configured IdentityProvider instance.
|
|
2911
|
+
*/
|
|
2912
|
+
getIdentityProvider() {
|
|
2913
|
+
return this.identityProvider;
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Dynamically sets or updates the active identity provider (e.g. SelfManaged, Cognito, Auth0, Okta).
|
|
2917
|
+
*
|
|
2918
|
+
* @param provider - Identity provider instance or undefined to disable
|
|
2919
|
+
*/
|
|
2920
|
+
setIdentityProvider(provider) {
|
|
2921
|
+
this.identityProvider = provider;
|
|
2922
|
+
if (provider instanceof SelfManagedIdentityProvider) {
|
|
2923
|
+
this.credentialsManager = provider.getCredentialsManager();
|
|
2924
|
+
} else {
|
|
2925
|
+
this.credentialsManager = void 0;
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Returns the underlying CredentialsManager instance if using Self-Managed identity.
|
|
1842
2930
|
*/
|
|
1843
2931
|
getCredentialsManager() {
|
|
1844
2932
|
return this.credentialsManager;
|
|
1845
2933
|
}
|
|
1846
2934
|
/**
|
|
1847
|
-
* Registers a new user with
|
|
1848
|
-
* Automatically
|
|
1849
|
-
* stores credentials, and creates a session (unless autoCreateSessionOnSignUp is false).
|
|
2935
|
+
* Registers a new user with the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
|
|
2936
|
+
* Automatically creates an active session unless autoCreateSessionOnSignUp is explicitly set to false.
|
|
1850
2937
|
*
|
|
1851
2938
|
* @param params - Registration parameters (identifier, password, email, username, metadata)
|
|
1852
|
-
* @returns Created user
|
|
2939
|
+
* @returns Created user identity and optional active session
|
|
1853
2940
|
* @throws WeakPasswordError if password does not meet policy requirements
|
|
1854
2941
|
* @throws UserAlreadyExistsError if identifier is already registered
|
|
1855
|
-
* @throws
|
|
2942
|
+
* @throws IdentityNotConfiguredError if identity authentication is not configured
|
|
1856
2943
|
*/
|
|
1857
2944
|
async signUp(params) {
|
|
1858
|
-
if (!this.
|
|
1859
|
-
throw new
|
|
2945
|
+
if (!this.identityProvider) {
|
|
2946
|
+
throw new IdentityNotConfiguredError();
|
|
2947
|
+
}
|
|
2948
|
+
const rawProviderName = params.provider || this.identityProvider.name;
|
|
2949
|
+
const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
|
|
2950
|
+
this.log("INFO", "Identity", `Processing signUp request via ${providerName}`, { identifier: params.identifier });
|
|
2951
|
+
const signUpEmail = params.email || (params.identifier?.includes("@") ? params.identifier : void 0);
|
|
2952
|
+
if (signUpEmail && this.accountHandler?.accountStorage?.findAccountsByEmail) {
|
|
2953
|
+
const normalizedEmail = signUpEmail.trim().toLowerCase();
|
|
2954
|
+
const existingAccounts = await this.accountHandler.accountStorage.findAccountsByEmail(normalizedEmail);
|
|
2955
|
+
if (existingAccounts.length > 0) {
|
|
2956
|
+
const existingProviderNames = existingAccounts.map((a) => a.provider);
|
|
2957
|
+
const hasCredentials = existingProviderNames.some(
|
|
2958
|
+
(p) => p === "credentials" || p === "self-managed" || p === "cognito"
|
|
2959
|
+
);
|
|
2960
|
+
if (!hasCredentials) {
|
|
2961
|
+
this.log("WARN", "Identity", `SignUp blocked: SSO account already exists for '${normalizedEmail}' via [${existingProviderNames.join(", ")}]`);
|
|
2962
|
+
throw new SSOAccountAlreadyExistsError(normalizedEmail, existingProviderNames);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
const user = await this.identityProvider.signUp(params);
|
|
2967
|
+
const isVerificationRequired = user.metadata?.requiresVerification === true || user.emailVerified === false && (user.metadata?.devConfirmationCode !== void 0 || this.identityProvider.name === "self-managed" || this.identityProvider.name === "cognito");
|
|
2968
|
+
if (isVerificationRequired) {
|
|
2969
|
+
this.log("INFO", "Identity", "SignUp requires verification \u2014 no session created", {
|
|
2970
|
+
identifier: user.identifier,
|
|
2971
|
+
email: user.email
|
|
2972
|
+
});
|
|
2973
|
+
return {
|
|
2974
|
+
user,
|
|
2975
|
+
requiresVerification: true,
|
|
2976
|
+
devConfirmationCode: user.metadata?.devConfirmationCode
|
|
2977
|
+
};
|
|
1860
2978
|
}
|
|
1861
|
-
this.log("INFO", "Credentials", "Processing signUp request", { identifier: params.identifier });
|
|
1862
|
-
const user = await this.credentialsManager.signUp(params);
|
|
1863
2979
|
const autoSession = this.config.credentials?.autoCreateSessionOnSignUp ?? true;
|
|
1864
2980
|
if (!autoSession) {
|
|
1865
2981
|
return { user };
|
|
@@ -1867,80 +2983,86 @@ var Lixa = class _Lixa {
|
|
|
1867
2983
|
const sessionId = randomBytes2(16).toString("hex");
|
|
1868
2984
|
const token = randomBytes2(32).toString("hex");
|
|
1869
2985
|
const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
|
|
2986
|
+
const activeProviderName = params.provider || user && user.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider.name);
|
|
2987
|
+
const accountKey = activeProviderName === "self-managed" ? "credentials" : activeProviderName || "credentials";
|
|
1870
2988
|
const credentialsAccount = {
|
|
1871
|
-
provider:
|
|
2989
|
+
provider: accountKey,
|
|
1872
2990
|
providerUserId: user.id,
|
|
1873
2991
|
email: user.email,
|
|
1874
|
-
accessToken: token,
|
|
1875
|
-
raw: {
|
|
1876
|
-
access_token: token,
|
|
1877
|
-
token_type: "Bearer",
|
|
1878
|
-
expires_in: ttl
|
|
1879
|
-
},
|
|
1880
2992
|
linkedAt: Date.now()
|
|
1881
2993
|
};
|
|
1882
2994
|
const session = {
|
|
1883
|
-
|
|
2995
|
+
sessionId,
|
|
1884
2996
|
userId: user.id,
|
|
1885
2997
|
email: user.email,
|
|
1886
|
-
|
|
1887
|
-
provider: "credentials",
|
|
1888
|
-
accounts: {
|
|
1889
|
-
credentials: credentialsAccount
|
|
1890
|
-
}
|
|
2998
|
+
provider: accountKey
|
|
1891
2999
|
};
|
|
3000
|
+
if (this.accountHandler?.accountStorage) {
|
|
3001
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
3002
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, this.sanitizeIdentityForStorage(credentialsAccount));
|
|
3003
|
+
}
|
|
1892
3004
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1893
3005
|
await sessionStorage.saveSession(sessionId, session, ttl);
|
|
1894
|
-
this.log("INFO", "
|
|
3006
|
+
this.log("INFO", "Identity", "Created session on signUp", { userId: user.id, sessionId, provider: accountKey });
|
|
1895
3007
|
return { user, sessionId, session };
|
|
1896
3008
|
}
|
|
1897
3009
|
/**
|
|
1898
|
-
* Authenticates a user with
|
|
1899
|
-
*
|
|
1900
|
-
* If account linking is configured with AUTO_LINK_BY_VERIFIED_EMAIL, merges with existing session.
|
|
3010
|
+
* Authenticates a user with credentials via the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta, or Composite).
|
|
3011
|
+
* Creates an active session and automatically links with existing sessions when AUTO_LINK_BY_VERIFIED_EMAIL is configured.
|
|
1901
3012
|
*
|
|
1902
|
-
* @param params - Sign-in parameters (identifier, password)
|
|
3013
|
+
* @param params - Sign-in parameters (identifier, password, optional provider)
|
|
1903
3014
|
* @returns Authenticated user, session ID, and session object
|
|
1904
3015
|
* @throws InvalidCredentialsError if authentication fails
|
|
1905
|
-
* @throws
|
|
3016
|
+
* @throws IdentityNotConfiguredError if identity authentication is not configured
|
|
1906
3017
|
*/
|
|
1907
3018
|
async signIn(params) {
|
|
1908
|
-
if (!this.
|
|
1909
|
-
throw new
|
|
3019
|
+
if (!this.identityProvider) {
|
|
3020
|
+
throw new IdentityNotConfiguredError();
|
|
1910
3021
|
}
|
|
1911
3022
|
const lookupKey = params.username || params.identifier;
|
|
1912
|
-
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
3023
|
+
const rawProviderName = params.provider || this.identityProvider.name;
|
|
3024
|
+
const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
|
|
3025
|
+
this.log("INFO", "Identity", `Processing signIn request via ${providerName}`, { identifier: lookupKey });
|
|
3026
|
+
const authResult = await this.identityProvider.signIn(params);
|
|
3027
|
+
const user = authResult.user;
|
|
3028
|
+
const tokens = authResult.tokens;
|
|
1918
3029
|
const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
|
|
1919
|
-
const token = randomBytes2(32).toString("hex");
|
|
3030
|
+
const token = tokens?.accessToken || randomBytes2(32).toString("hex");
|
|
3031
|
+
const rawTokens = {
|
|
3032
|
+
access_token: token,
|
|
3033
|
+
token_type: tokens?.tokenType || "Bearer",
|
|
3034
|
+
expires_in: tokens?.expiresIn || ttl,
|
|
3035
|
+
id_token: tokens?.idToken,
|
|
3036
|
+
refresh_token: tokens?.refreshToken,
|
|
3037
|
+
...tokens?.raw
|
|
3038
|
+
};
|
|
3039
|
+
const activeProviderName = params.provider || user && user.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider.name);
|
|
3040
|
+
const accountKey = activeProviderName === "self-managed" ? "credentials" : activeProviderName || "credentials";
|
|
1920
3041
|
const credentialsAccount = {
|
|
1921
|
-
provider:
|
|
3042
|
+
provider: accountKey,
|
|
1922
3043
|
providerUserId: user.id,
|
|
1923
3044
|
email: user.email,
|
|
1924
|
-
accessToken: token,
|
|
1925
|
-
raw: {
|
|
1926
|
-
access_token: token,
|
|
1927
|
-
token_type: "Bearer",
|
|
1928
|
-
expires_in: ttl
|
|
1929
|
-
},
|
|
1930
3045
|
linkedAt: Date.now()
|
|
1931
3046
|
};
|
|
1932
3047
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1933
|
-
const
|
|
1934
|
-
const
|
|
3048
|
+
const linkingConfig = this.config.accountLinking;
|
|
3049
|
+
const linkingMode = typeof linkingConfig === "object" ? linkingConfig?.mode : void 0;
|
|
3050
|
+
const isAutoLink = linkingConfig === true || linkingConfig === "linkByEmail" || linkingConfig === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" || linkingMode === "linkByEmail";
|
|
1935
3051
|
if (isAutoLink && user.email && sessionStorage.getSessionByEmail) {
|
|
1936
3052
|
const existing = await sessionStorage.getSessionByEmail(user.email);
|
|
1937
3053
|
if (existing) {
|
|
1938
3054
|
const mergedSession = { ...existing.session };
|
|
1939
|
-
mergedSession.accounts = { ...mergedSession.accounts || {}, credentials: credentialsAccount };
|
|
1940
3055
|
if (!mergedSession.userId) mergedSession.userId = user.id;
|
|
1941
3056
|
if (!mergedSession.email) mergedSession.email = user.email;
|
|
3057
|
+
mergedSession.provider = accountKey;
|
|
3058
|
+
mergedSession.token = token;
|
|
3059
|
+
mergedSession.raw = rawTokens;
|
|
3060
|
+
if (this.accountHandler?.accountStorage) {
|
|
3061
|
+
const userKey = this.getUserKeyFromSession(mergedSession);
|
|
3062
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, this.sanitizeIdentityForStorage(credentialsAccount));
|
|
3063
|
+
}
|
|
1942
3064
|
await sessionStorage.saveSession(existing.sessionId, mergedSession, ttl);
|
|
1943
|
-
this.log("INFO", "AccountLinking",
|
|
3065
|
+
this.log("INFO", "AccountLinking", `Linked ${providerName} to existing session by email`, {
|
|
1944
3066
|
sessionId: existing.sessionId,
|
|
1945
3067
|
email: user.email
|
|
1946
3068
|
});
|
|
@@ -1952,52 +3074,228 @@ var Lixa = class _Lixa {
|
|
|
1952
3074
|
}
|
|
1953
3075
|
}
|
|
1954
3076
|
const sessionId = randomBytes2(16).toString("hex");
|
|
3077
|
+
let targetUserId = user.id;
|
|
3078
|
+
const allAccounts = {
|
|
3079
|
+
[accountKey]: credentialsAccount
|
|
3080
|
+
};
|
|
3081
|
+
if (this.accountHandler?.accountStorage) {
|
|
3082
|
+
if (user.email && this.accountHandler.accountStorage.findAccountsByEmail) {
|
|
3083
|
+
const matches = await this.accountHandler.accountStorage.findAccountsByEmail(user.email);
|
|
3084
|
+
if (matches.length > 0) {
|
|
3085
|
+
targetUserId = matches[0].userId;
|
|
3086
|
+
for (const match of matches) {
|
|
3087
|
+
const userAccs = await this.accountHandler.accountStorage.getUserAccounts(match.userId);
|
|
3088
|
+
if (userAccs) {
|
|
3089
|
+
for (const [k, v] of Object.entries(userAccs)) {
|
|
3090
|
+
if (k !== accountKey && !allAccounts[k]) {
|
|
3091
|
+
allAccounts[k] = v;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
const directAccounts = await this.accountHandler.accountStorage.getUserAccounts(user.id);
|
|
3099
|
+
if (directAccounts) {
|
|
3100
|
+
for (const [k, v] of Object.entries(directAccounts)) {
|
|
3101
|
+
if (k !== accountKey && !allAccounts[k]) {
|
|
3102
|
+
allAccounts[k] = v;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
3107
|
+
targetUserId,
|
|
3108
|
+
accountKey,
|
|
3109
|
+
this.sanitizeIdentityForStorage(credentialsAccount)
|
|
3110
|
+
);
|
|
3111
|
+
for (const [pKey, pAcc] of Object.entries(allAccounts)) {
|
|
3112
|
+
if (pKey !== accountKey) {
|
|
3113
|
+
await this.accountHandler.accountStorage.saveAccount(
|
|
3114
|
+
targetUserId,
|
|
3115
|
+
pKey,
|
|
3116
|
+
this.sanitizeIdentityForStorage(pAcc)
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
1955
3121
|
const session = {
|
|
1956
|
-
|
|
1957
|
-
userId:
|
|
3122
|
+
sessionId,
|
|
3123
|
+
userId: targetUserId,
|
|
1958
3124
|
email: user.email,
|
|
3125
|
+
provider: accountKey,
|
|
1959
3126
|
token,
|
|
1960
|
-
|
|
1961
|
-
accounts: {
|
|
1962
|
-
credentials: credentialsAccount
|
|
1963
|
-
}
|
|
3127
|
+
raw: rawTokens
|
|
1964
3128
|
};
|
|
1965
3129
|
await sessionStorage.saveSession(sessionId, session, ttl);
|
|
1966
|
-
this.log("INFO", "
|
|
3130
|
+
this.log("INFO", "Identity", `User signed in successfully via ${accountKey}`, { userId: targetUserId, sessionId });
|
|
1967
3131
|
return { user, sessionId, session };
|
|
1968
3132
|
}
|
|
1969
3133
|
/**
|
|
1970
3134
|
* Verifies username/email and password credentials without generating a session.
|
|
1971
3135
|
*
|
|
1972
|
-
* @param params - Verification parameters (identifier, password)
|
|
1973
|
-
* @returns User credentials
|
|
1974
|
-
* @throws
|
|
3136
|
+
* @param params - Verification parameters (identifier, password, optional provider)
|
|
3137
|
+
* @returns User credentials/identity or null if invalid
|
|
3138
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
1975
3139
|
*/
|
|
1976
3140
|
async verifyCredentials(params) {
|
|
1977
|
-
if (!this.
|
|
1978
|
-
throw new
|
|
3141
|
+
if (!this.identityProvider) {
|
|
3142
|
+
throw new IdentityNotConfiguredError();
|
|
3143
|
+
}
|
|
3144
|
+
if (typeof this.identityProvider.verifyCredentials === "function") {
|
|
3145
|
+
return this.identityProvider.verifyCredentials(params);
|
|
3146
|
+
}
|
|
3147
|
+
try {
|
|
3148
|
+
const result = await this.identityProvider.signIn(params);
|
|
3149
|
+
return result.user;
|
|
3150
|
+
} catch {
|
|
3151
|
+
return null;
|
|
1979
3152
|
}
|
|
1980
|
-
return this.credentialsManager.verifyCredentials(params);
|
|
1981
3153
|
}
|
|
1982
3154
|
/**
|
|
1983
3155
|
* Updates a user's password after verifying the current password and enforcing policy on the new password.
|
|
1984
3156
|
*
|
|
1985
|
-
* @param params - Change password parameters (userId/identifier, oldPassword, newPassword)
|
|
3157
|
+
* @param params - Change password parameters (userId/identifier, oldPassword, newPassword, optional provider)
|
|
1986
3158
|
* @returns True if password was updated successfully
|
|
1987
3159
|
* @throws UserNotFoundError if user is not found
|
|
1988
3160
|
* @throws InvalidCredentialsError if current password is incorrect
|
|
1989
3161
|
* @throws WeakPasswordError if new password does not meet policy requirements
|
|
1990
|
-
* @throws
|
|
3162
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
1991
3163
|
*/
|
|
1992
3164
|
async changePassword(params) {
|
|
1993
|
-
if (!this.
|
|
1994
|
-
throw new
|
|
3165
|
+
if (!this.identityProvider) {
|
|
3166
|
+
throw new IdentityNotConfiguredError();
|
|
1995
3167
|
}
|
|
1996
|
-
this.
|
|
3168
|
+
if (typeof this.identityProvider.changePassword !== "function") {
|
|
3169
|
+
throw new IdentityProviderError(
|
|
3170
|
+
`Password change is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
3171
|
+
this.identityProvider.name
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
this.log("INFO", "Identity", "Processing changePassword request", {
|
|
1997
3175
|
userId: params.userId,
|
|
1998
3176
|
identifier: params.identifier
|
|
1999
3177
|
});
|
|
2000
|
-
return this.
|
|
3178
|
+
return this.identityProvider.changePassword(params);
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Initiates a password reset (forgot password flow) through the configured identity provider.
|
|
3182
|
+
*
|
|
3183
|
+
* @param params - Forgot password parameters (identifier, optional metadata, optional provider)
|
|
3184
|
+
* @returns ForgotPasswordResult indicating status and delivery medium (email/SMS)
|
|
3185
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
3186
|
+
*/
|
|
3187
|
+
async forgotPassword(params) {
|
|
3188
|
+
if (!this.identityProvider) {
|
|
3189
|
+
throw new IdentityNotConfiguredError();
|
|
3190
|
+
}
|
|
3191
|
+
if (typeof this.identityProvider.forgotPassword !== "function") {
|
|
3192
|
+
throw new IdentityProviderError(
|
|
3193
|
+
`Password reset request is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
3194
|
+
this.identityProvider.name
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
this.log("INFO", "Identity", "Processing forgotPassword request", { identifier: params.identifier });
|
|
3198
|
+
return this.identityProvider.forgotPassword(params);
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Confirms a password reset using a verification code / token and sets the new password.
|
|
3202
|
+
*
|
|
3203
|
+
* @param params - Verification code, identifier, new password, and optional provider
|
|
3204
|
+
* @returns True if password reset was successfully confirmed
|
|
3205
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
3206
|
+
*/
|
|
3207
|
+
async confirmPasswordReset(params) {
|
|
3208
|
+
if (!this.identityProvider) {
|
|
3209
|
+
throw new IdentityNotConfiguredError();
|
|
3210
|
+
}
|
|
3211
|
+
if (typeof this.identityProvider.confirmPasswordReset !== "function") {
|
|
3212
|
+
throw new IdentityProviderError(
|
|
3213
|
+
`Confirm password reset is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
3214
|
+
this.identityProvider.name
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
this.log("INFO", "Identity", "Processing confirmPasswordReset request", { identifier: params.identifier });
|
|
3218
|
+
return this.identityProvider.confirmPasswordReset(params);
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Confirms user account registration using a verification code.
|
|
3222
|
+
*
|
|
3223
|
+
* @param params - Verification parameters including identifier, confirmationCode, and optional provider
|
|
3224
|
+
* @returns True if confirmation was successful
|
|
3225
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
3226
|
+
* @throws IdentityProviderError if identity provider does not support confirmation
|
|
3227
|
+
*/
|
|
3228
|
+
async confirmSignUp(params) {
|
|
3229
|
+
if (!this.identityProvider) {
|
|
3230
|
+
throw new IdentityNotConfiguredError();
|
|
3231
|
+
}
|
|
3232
|
+
if (typeof this.identityProvider.confirmSignUp !== "function") {
|
|
3233
|
+
throw new IdentityProviderError(
|
|
3234
|
+
`Account confirmation is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
3235
|
+
this.identityProvider.name
|
|
3236
|
+
);
|
|
3237
|
+
}
|
|
3238
|
+
this.log("INFO", "Identity", "Processing confirmSignUp request", { identifier: params.identifier });
|
|
3239
|
+
return this.identityProvider.confirmSignUp(params);
|
|
3240
|
+
}
|
|
3241
|
+
/**
|
|
3242
|
+
* Resends the sign up confirmation code to the user.
|
|
3243
|
+
*
|
|
3244
|
+
* @param params - Resend parameters including user identifier and optional provider
|
|
3245
|
+
* @returns Details about the code delivery (medium, destination)
|
|
3246
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
3247
|
+
* @throws IdentityProviderError if identity provider does not support resending codes
|
|
3248
|
+
*/
|
|
3249
|
+
async resendConfirmationCode(params) {
|
|
3250
|
+
if (!this.identityProvider) {
|
|
3251
|
+
throw new IdentityNotConfiguredError();
|
|
3252
|
+
}
|
|
3253
|
+
if (typeof this.identityProvider.resendConfirmationCode !== "function") {
|
|
3254
|
+
throw new IdentityProviderError(
|
|
3255
|
+
`Resending confirmation code is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
3256
|
+
this.identityProvider.name
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
this.log("INFO", "Identity", "Processing resendConfirmationCode request", { identifier: params.identifier });
|
|
3260
|
+
return this.identityProvider.resendConfirmationCode(params);
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Retrieves a user by their unique ID from the configured identity provider.
|
|
3264
|
+
*
|
|
3265
|
+
* @param id - Unique user ID
|
|
3266
|
+
* @param provider - Optional provider key when using CompositeIdentityProvider
|
|
3267
|
+
* @returns UserIdentity or null if not found
|
|
3268
|
+
*/
|
|
3269
|
+
async getUserById(id, provider) {
|
|
3270
|
+
if (!this.identityProvider || typeof this.identityProvider.getUserById !== "function") {
|
|
3271
|
+
return null;
|
|
3272
|
+
}
|
|
3273
|
+
return this.identityProvider.getUserById(id, provider);
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Retrieves a user by their identifier (username or email) from the configured identity provider.
|
|
3277
|
+
*
|
|
3278
|
+
* @param identifier - Username or email string
|
|
3279
|
+
* @param provider - Optional provider key when using CompositeIdentityProvider
|
|
3280
|
+
* @returns UserIdentity or null if not found
|
|
3281
|
+
*/
|
|
3282
|
+
async getUserByIdentifier(identifier, provider) {
|
|
3283
|
+
if (!this.identityProvider || typeof this.identityProvider.getUserByIdentifier !== "function") {
|
|
3284
|
+
return null;
|
|
3285
|
+
}
|
|
3286
|
+
return this.identityProvider.getUserByIdentifier(identifier, provider);
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Deletes a user account from the configured identity provider.
|
|
3290
|
+
*
|
|
3291
|
+
* @param id - Unique user ID
|
|
3292
|
+
* @param provider - Optional provider key when using CompositeIdentityProvider
|
|
3293
|
+
*/
|
|
3294
|
+
async deleteUser(id, provider) {
|
|
3295
|
+
if (!this.identityProvider || typeof this.identityProvider.deleteUser !== "function") {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
await this.identityProvider.deleteUser(id, provider);
|
|
2001
3299
|
}
|
|
2002
3300
|
findProviderByType(providerType) {
|
|
2003
3301
|
for (const [key, value] of Object.entries(this.config.providers || {})) {
|
|
@@ -2009,125 +3307,6 @@ var Lixa = class _Lixa {
|
|
|
2009
3307
|
}
|
|
2010
3308
|
};
|
|
2011
3309
|
|
|
2012
|
-
// src/utils/cookies.ts
|
|
2013
|
-
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
2014
|
-
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
2015
|
-
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
2016
|
-
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
2017
|
-
function isProductionEnvironment() {
|
|
2018
|
-
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
2019
|
-
}
|
|
2020
|
-
function serializeCookie(name, value, options) {
|
|
2021
|
-
const isProd = isProductionEnvironment();
|
|
2022
|
-
const path = options?.path ?? "/";
|
|
2023
|
-
const httpOnly = options?.httpOnly ?? true;
|
|
2024
|
-
const secure = options?.secure ?? isProd;
|
|
2025
|
-
const sameSite = options?.sameSite ?? "lax";
|
|
2026
|
-
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
2027
|
-
if (path) {
|
|
2028
|
-
parts.push(`Path=${path}`);
|
|
2029
|
-
}
|
|
2030
|
-
if (typeof options?.maxAge === "number") {
|
|
2031
|
-
parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
|
|
2032
|
-
const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
|
|
2033
|
-
parts.push(`Expires=${expires}`);
|
|
2034
|
-
}
|
|
2035
|
-
if (options?.domain) {
|
|
2036
|
-
parts.push(`Domain=${options.domain}`);
|
|
2037
|
-
}
|
|
2038
|
-
if (httpOnly) {
|
|
2039
|
-
parts.push("HttpOnly");
|
|
2040
|
-
}
|
|
2041
|
-
if (secure) {
|
|
2042
|
-
parts.push("Secure");
|
|
2043
|
-
}
|
|
2044
|
-
if (sameSite) {
|
|
2045
|
-
const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
|
|
2046
|
-
parts.push(`SameSite=${capitalized}`);
|
|
2047
|
-
}
|
|
2048
|
-
return parts.join("; ");
|
|
2049
|
-
}
|
|
2050
|
-
function createSessionCookie(sessionId, options) {
|
|
2051
|
-
const isProd = isProductionEnvironment();
|
|
2052
|
-
const resolvedOptions = {
|
|
2053
|
-
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
2054
|
-
path: options?.path ?? "/",
|
|
2055
|
-
maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
2056
|
-
httpOnly: options?.httpOnly ?? true,
|
|
2057
|
-
secure: options?.secure ?? isProd,
|
|
2058
|
-
sameSite: options?.sameSite ?? "lax",
|
|
2059
|
-
domain: options?.domain
|
|
2060
|
-
};
|
|
2061
|
-
const name = resolvedOptions.name;
|
|
2062
|
-
const header = serializeCookie(name, sessionId, resolvedOptions);
|
|
2063
|
-
return {
|
|
2064
|
-
name,
|
|
2065
|
-
value: sessionId,
|
|
2066
|
-
options: resolvedOptions,
|
|
2067
|
-
header
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
function clearSessionCookie(options) {
|
|
2071
|
-
const isProd = isProductionEnvironment();
|
|
2072
|
-
const resolvedOptions = {
|
|
2073
|
-
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
2074
|
-
path: options?.path ?? "/",
|
|
2075
|
-
maxAge: 0,
|
|
2076
|
-
httpOnly: options?.httpOnly ?? true,
|
|
2077
|
-
secure: options?.secure ?? isProd,
|
|
2078
|
-
sameSite: options?.sameSite ?? "lax",
|
|
2079
|
-
domain: options?.domain
|
|
2080
|
-
};
|
|
2081
|
-
const name = resolvedOptions.name;
|
|
2082
|
-
const header = serializeCookie(name, "", resolvedOptions);
|
|
2083
|
-
return {
|
|
2084
|
-
name,
|
|
2085
|
-
value: "",
|
|
2086
|
-
options: resolvedOptions,
|
|
2087
|
-
header
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
function createStateCookie(state, options) {
|
|
2091
|
-
const isProd = isProductionEnvironment();
|
|
2092
|
-
const resolvedOptions = {
|
|
2093
|
-
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
2094
|
-
path: options?.path ?? "/",
|
|
2095
|
-
maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
2096
|
-
httpOnly: options?.httpOnly ?? true,
|
|
2097
|
-
secure: options?.secure ?? isProd,
|
|
2098
|
-
sameSite: options?.sameSite ?? "lax",
|
|
2099
|
-
domain: options?.domain
|
|
2100
|
-
};
|
|
2101
|
-
const name = resolvedOptions.name;
|
|
2102
|
-
const header = serializeCookie(name, state, resolvedOptions);
|
|
2103
|
-
return {
|
|
2104
|
-
name,
|
|
2105
|
-
value: state,
|
|
2106
|
-
options: resolvedOptions,
|
|
2107
|
-
header
|
|
2108
|
-
};
|
|
2109
|
-
}
|
|
2110
|
-
function clearStateCookie(options) {
|
|
2111
|
-
const isProd = isProductionEnvironment();
|
|
2112
|
-
const resolvedOptions = {
|
|
2113
|
-
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
2114
|
-
path: options?.path ?? "/",
|
|
2115
|
-
maxAge: 0,
|
|
2116
|
-
httpOnly: options?.httpOnly ?? true,
|
|
2117
|
-
secure: options?.secure ?? isProd,
|
|
2118
|
-
sameSite: options?.sameSite ?? "lax",
|
|
2119
|
-
domain: options?.domain
|
|
2120
|
-
};
|
|
2121
|
-
const name = resolvedOptions.name;
|
|
2122
|
-
const header = serializeCookie(name, "", resolvedOptions);
|
|
2123
|
-
return {
|
|
2124
|
-
name,
|
|
2125
|
-
value: "",
|
|
2126
|
-
options: resolvedOptions,
|
|
2127
|
-
header
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
3310
|
// src/credentials/index.ts
|
|
2132
3311
|
var credentials_exports = {};
|
|
2133
3312
|
__export(credentials_exports, {
|
|
@@ -2137,9 +3316,19 @@ __export(credentials_exports, {
|
|
|
2137
3316
|
ScryptPasswordHasher: () => ScryptPasswordHasher,
|
|
2138
3317
|
validatePasswordPolicy: () => validatePasswordPolicy
|
|
2139
3318
|
});
|
|
3319
|
+
|
|
3320
|
+
// src/identity/index.ts
|
|
3321
|
+
var identity_exports = {};
|
|
3322
|
+
__export(identity_exports, {
|
|
3323
|
+
CompositeIdentityProvider: () => CompositeIdentityProvider,
|
|
3324
|
+
SelfManagedIdentityProvider: () => SelfManagedIdentityProvider
|
|
3325
|
+
});
|
|
2140
3326
|
export {
|
|
3327
|
+
AccountLinkingChallengeRequiredError,
|
|
2141
3328
|
AccountLinkingStrategy,
|
|
3329
|
+
AccountNotVerifiedError,
|
|
2142
3330
|
AccountUnlinkError,
|
|
3331
|
+
CompositeIdentityProvider,
|
|
2143
3332
|
CredentialsManager,
|
|
2144
3333
|
CredentialsNotConfiguredError,
|
|
2145
3334
|
DEFAULT_SESSION_COOKIE_NAME,
|
|
@@ -2147,6 +3336,8 @@ export {
|
|
|
2147
3336
|
DEFAULT_STATE_COOKIE_NAME,
|
|
2148
3337
|
DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
2149
3338
|
EmailNotVerifiedError,
|
|
3339
|
+
IdentityNotConfiguredError,
|
|
3340
|
+
IdentityProviderError,
|
|
2150
3341
|
InvalidCredentialsError,
|
|
2151
3342
|
InvalidOAuthCallbackError,
|
|
2152
3343
|
InvalidProviderConfigError,
|
|
@@ -2157,7 +3348,9 @@ export {
|
|
|
2157
3348
|
Pbkdf2PasswordHasher,
|
|
2158
3349
|
ProviderNotConfiguredError,
|
|
2159
3350
|
RefreshTokenError,
|
|
3351
|
+
SSOAccountAlreadyExistsError,
|
|
2160
3352
|
ScryptPasswordHasher,
|
|
3353
|
+
SelfManagedIdentityProvider,
|
|
2161
3354
|
SessionNotFoundError,
|
|
2162
3355
|
TokenExchangeError,
|
|
2163
3356
|
UserAlreadyExistsError,
|
|
@@ -2172,6 +3365,7 @@ export {
|
|
|
2172
3365
|
determineProviderFromIssuer,
|
|
2173
3366
|
extractUserInfo,
|
|
2174
3367
|
fetchUserInfo,
|
|
3368
|
+
identity_exports as identity,
|
|
2175
3369
|
isProductionEnvironment,
|
|
2176
3370
|
serializeCookie,
|
|
2177
3371
|
validatePasswordPolicy
|