@spfn/auth 0.2.0-beta.67 → 0.2.0-beta.69
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 +1 -1
- package/dist/{authenticate-Cn5krz5U.d.ts → authenticate-NhEb_j27.d.ts} +2 -22
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -9
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +1 -1
- package/dist/nextjs/api.js.map +1 -1
- package/dist/server.d.ts +41 -24
- package/dist/server.js +164 -118
- package/dist/server.js.map +1 -1
- package/package.json +9 -10
package/dist/server.js
CHANGED
|
@@ -4471,7 +4471,8 @@ var init_schema3 = __esm({
|
|
|
4471
4471
|
});
|
|
4472
4472
|
PasswordSchema = Type.String({
|
|
4473
4473
|
minLength: 8,
|
|
4474
|
-
|
|
4474
|
+
maxLength: 72,
|
|
4475
|
+
description: "User password (8\u201372 characters). bcrypt silently ignores bytes past 72, so longer inputs are rejected rather than truncated."
|
|
4475
4476
|
});
|
|
4476
4477
|
TargetTypeSchema = Type.Union([
|
|
4477
4478
|
Type.Literal("email"),
|
|
@@ -5590,12 +5591,13 @@ var init_users_repository = __esm({
|
|
|
5590
5591
|
|
|
5591
5592
|
// src/server/repositories/keys.repository.ts
|
|
5592
5593
|
import { BaseRepository as BaseRepository2 } from "@spfn/core/db";
|
|
5593
|
-
import { eq as eq2, and as and2 } from "drizzle-orm";
|
|
5594
|
-
var KeysRepository, keysRepository;
|
|
5594
|
+
import { eq as eq2, and as and2, or, isNull, lt } from "drizzle-orm";
|
|
5595
|
+
var LAST_USED_THROTTLE_MS, KeysRepository, keysRepository;
|
|
5595
5596
|
var init_keys_repository = __esm({
|
|
5596
5597
|
"src/server/repositories/keys.repository.ts"() {
|
|
5597
5598
|
"use strict";
|
|
5598
5599
|
init_user_public_keys();
|
|
5600
|
+
LAST_USED_THROTTLE_MS = 6e4;
|
|
5599
5601
|
KeysRepository = class extends BaseRepository2 {
|
|
5600
5602
|
/**
|
|
5601
5603
|
* Key ID와 User ID로 공개키 조회
|
|
@@ -5699,13 +5701,24 @@ var init_keys_repository = __esm({
|
|
|
5699
5701
|
}
|
|
5700
5702
|
/**
|
|
5701
5703
|
* Primary key로 마지막 사용 시간 업데이트 (authenticate용)
|
|
5702
|
-
* Write primary
|
|
5704
|
+
* Write primary 사용.
|
|
5705
|
+
*
|
|
5706
|
+
* Throttled: only writes when lastUsedAt is stale (older than
|
|
5707
|
+
* LAST_USED_THROTTLE_MS), so a busy key isn't UPDATEd on every request. The
|
|
5708
|
+
* throttle lives in the WHERE clause — atomic, no read-then-write race. No
|
|
5709
|
+
* RETURNING (callers fire-and-forget and discard the row).
|
|
5703
5710
|
*/
|
|
5704
5711
|
async updateLastUsedById(id11) {
|
|
5705
|
-
const
|
|
5712
|
+
const staleBefore = new Date(Date.now() - LAST_USED_THROTTLE_MS);
|
|
5713
|
+
await this.db.update(userPublicKeys).set({
|
|
5706
5714
|
lastUsedAt: /* @__PURE__ */ new Date()
|
|
5707
|
-
}).where(
|
|
5708
|
-
|
|
5715
|
+
}).where(and2(
|
|
5716
|
+
eq2(userPublicKeys.id, id11),
|
|
5717
|
+
or(
|
|
5718
|
+
isNull(userPublicKeys.lastUsedAt),
|
|
5719
|
+
lt(userPublicKeys.lastUsedAt, staleBefore)
|
|
5720
|
+
)
|
|
5721
|
+
));
|
|
5709
5722
|
}
|
|
5710
5723
|
};
|
|
5711
5724
|
keysRepository = new KeysRepository();
|
|
@@ -5714,7 +5727,7 @@ var init_keys_repository = __esm({
|
|
|
5714
5727
|
|
|
5715
5728
|
// src/server/repositories/verification-codes.repository.ts
|
|
5716
5729
|
import { BaseRepository as BaseRepository3 } from "@spfn/core/db";
|
|
5717
|
-
import { eq as eq3, and as and3, lt, gt, isNull } from "drizzle-orm";
|
|
5730
|
+
import { eq as eq3, and as and3, lt as lt2, gt, isNull as isNull2 } from "drizzle-orm";
|
|
5718
5731
|
var VerificationCodesRepository, verificationCodesRepository;
|
|
5719
5732
|
var init_verification_codes_repository = __esm({
|
|
5720
5733
|
"src/server/repositories/verification-codes.repository.ts"() {
|
|
@@ -5732,7 +5745,7 @@ var init_verification_codes_repository = __esm({
|
|
|
5732
5745
|
and3(
|
|
5733
5746
|
eq3(verificationCodes.target, target),
|
|
5734
5747
|
eq3(verificationCodes.purpose, purpose),
|
|
5735
|
-
|
|
5748
|
+
isNull2(verificationCodes.usedAt),
|
|
5736
5749
|
gt(verificationCodes.expiresAt, now)
|
|
5737
5750
|
)
|
|
5738
5751
|
).limit(1);
|
|
@@ -5787,7 +5800,7 @@ var init_verification_codes_repository = __esm({
|
|
|
5787
5800
|
*/
|
|
5788
5801
|
async deleteExpired() {
|
|
5789
5802
|
const now = /* @__PURE__ */ new Date();
|
|
5790
|
-
const result = await this.db.delete(verificationCodes).where(
|
|
5803
|
+
const result = await this.db.delete(verificationCodes).where(lt2(verificationCodes.expiresAt, now)).returning();
|
|
5791
5804
|
return result.length;
|
|
5792
5805
|
}
|
|
5793
5806
|
/**
|
|
@@ -5802,7 +5815,7 @@ var init_verification_codes_repository = __esm({
|
|
|
5802
5815
|
and3(
|
|
5803
5816
|
eq3(verificationCodes.target, target),
|
|
5804
5817
|
eq3(verificationCodes.purpose, purpose),
|
|
5805
|
-
|
|
5818
|
+
isNull2(verificationCodes.usedAt)
|
|
5806
5819
|
)
|
|
5807
5820
|
).returning();
|
|
5808
5821
|
return result.length;
|
|
@@ -6048,7 +6061,7 @@ var init_role_permissions_repository = __esm({
|
|
|
6048
6061
|
|
|
6049
6062
|
// src/server/repositories/user-permissions.repository.ts
|
|
6050
6063
|
import { BaseRepository as BaseRepository7 } from "@spfn/core/db";
|
|
6051
|
-
import { eq as eq7, and as and5, or, isNull as
|
|
6064
|
+
import { eq as eq7, and as and5, or as or2, isNull as isNull3, isNotNull, lt as lt3, gt as gt2 } from "drizzle-orm";
|
|
6052
6065
|
var UserPermissionsRepository, userPermissionsRepository;
|
|
6053
6066
|
var init_user_permissions_repository = __esm({
|
|
6054
6067
|
"src/server/repositories/user-permissions.repository.ts"() {
|
|
@@ -6070,8 +6083,8 @@ var init_user_permissions_repository = __esm({
|
|
|
6070
6083
|
return this.readDb.select().from(userPermissions).where(
|
|
6071
6084
|
and5(
|
|
6072
6085
|
eq7(userPermissions.userId, userId),
|
|
6073
|
-
|
|
6074
|
-
|
|
6086
|
+
or2(
|
|
6087
|
+
isNull3(userPermissions.expiresAt),
|
|
6075
6088
|
gt2(userPermissions.expiresAt, now)
|
|
6076
6089
|
)
|
|
6077
6090
|
)
|
|
@@ -6133,7 +6146,7 @@ var init_user_permissions_repository = __esm({
|
|
|
6133
6146
|
const result = await this.db.delete(userPermissions).where(
|
|
6134
6147
|
and5(
|
|
6135
6148
|
isNotNull(userPermissions.expiresAt),
|
|
6136
|
-
|
|
6149
|
+
lt3(userPermissions.expiresAt, now)
|
|
6137
6150
|
)
|
|
6138
6151
|
).returning();
|
|
6139
6152
|
return result.length;
|
|
@@ -6278,7 +6291,7 @@ var init_user_profiles_repository = __esm({
|
|
|
6278
6291
|
});
|
|
6279
6292
|
|
|
6280
6293
|
// src/server/repositories/invitations.repository.ts
|
|
6281
|
-
import { eq as eq9, and as and6, lt as
|
|
6294
|
+
import { eq as eq9, and as and6, lt as lt4, desc, sql as sql3 } from "drizzle-orm";
|
|
6282
6295
|
import { BaseRepository as BaseRepository9 } from "@spfn/core/db";
|
|
6283
6296
|
var InvitationsRepository, invitationsRepository;
|
|
6284
6297
|
var init_invitations_repository = __esm({
|
|
@@ -6372,7 +6385,7 @@ var init_invitations_repository = __esm({
|
|
|
6372
6385
|
}).where(
|
|
6373
6386
|
and6(
|
|
6374
6387
|
eq9(userInvitations.status, "pending"),
|
|
6375
|
-
|
|
6388
|
+
lt4(userInvitations.expiresAt, now)
|
|
6376
6389
|
)
|
|
6377
6390
|
).returning();
|
|
6378
6391
|
return result.length;
|
|
@@ -6847,7 +6860,7 @@ import { defineRouter as defineRouter5 } from "@spfn/core/route";
|
|
|
6847
6860
|
init_schema3();
|
|
6848
6861
|
|
|
6849
6862
|
// src/server/helpers/password.ts
|
|
6850
|
-
import bcrypt from "
|
|
6863
|
+
import * as bcrypt from "@node-rs/bcrypt";
|
|
6851
6864
|
import { env } from "@spfn/auth/config";
|
|
6852
6865
|
import { createPasswordParser } from "@spfn/core/env";
|
|
6853
6866
|
async function hashPassword(password) {
|
|
@@ -6856,14 +6869,14 @@ async function hashPassword(password) {
|
|
|
6856
6869
|
}
|
|
6857
6870
|
return bcrypt.hash(password, env.SPFN_AUTH_BCRYPT_SALT_ROUNDS);
|
|
6858
6871
|
}
|
|
6859
|
-
async function verifyPassword(password,
|
|
6872
|
+
async function verifyPassword(password, hash2) {
|
|
6860
6873
|
if (!password || password.length === 0) {
|
|
6861
6874
|
throw new Error("Password cannot be empty");
|
|
6862
6875
|
}
|
|
6863
|
-
if (!
|
|
6876
|
+
if (!hash2 || hash2.length === 0) {
|
|
6864
6877
|
throw new Error("Hash cannot be empty");
|
|
6865
6878
|
}
|
|
6866
|
-
return bcrypt.
|
|
6879
|
+
return bcrypt.verify(password, hash2);
|
|
6867
6880
|
}
|
|
6868
6881
|
var passwordValidator = createPasswordParser({
|
|
6869
6882
|
minLength: 8,
|
|
@@ -6902,21 +6915,37 @@ function validatePasswordStrength(password) {
|
|
|
6902
6915
|
import jwt from "jsonwebtoken";
|
|
6903
6916
|
import crypto2 from "crypto";
|
|
6904
6917
|
import { env as env2 } from "@spfn/auth/config";
|
|
6918
|
+
var PUBLIC_KEY_CACHE_MAX = 1e3;
|
|
6919
|
+
var publicKeyCache = /* @__PURE__ */ new Map();
|
|
6920
|
+
function getPublicKeyObject(publicKeyB64) {
|
|
6921
|
+
const cached = publicKeyCache.get(publicKeyB64);
|
|
6922
|
+
if (cached) {
|
|
6923
|
+
return cached;
|
|
6924
|
+
}
|
|
6925
|
+
const keyObject = crypto2.createPublicKey({
|
|
6926
|
+
key: Buffer.from(publicKeyB64, "base64"),
|
|
6927
|
+
format: "der",
|
|
6928
|
+
type: "spki"
|
|
6929
|
+
});
|
|
6930
|
+
if (publicKeyCache.size >= PUBLIC_KEY_CACHE_MAX) {
|
|
6931
|
+
const oldest = publicKeyCache.keys().next().value;
|
|
6932
|
+
if (oldest !== void 0) {
|
|
6933
|
+
publicKeyCache.delete(oldest);
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
publicKeyCache.set(publicKeyB64, keyObject);
|
|
6937
|
+
return keyObject;
|
|
6938
|
+
}
|
|
6905
6939
|
function generateToken(payload) {
|
|
6906
6940
|
return jwt.sign(payload, env2.SPFN_AUTH_JWT_SECRET, {
|
|
6907
6941
|
expiresIn: env2.SPFN_AUTH_JWT_EXPIRES_IN
|
|
6908
6942
|
});
|
|
6909
6943
|
}
|
|
6910
6944
|
function verifyToken(token) {
|
|
6911
|
-
return jwt.verify(token, env2.SPFN_AUTH_JWT_SECRET);
|
|
6945
|
+
return jwt.verify(token, env2.SPFN_AUTH_JWT_SECRET, { algorithms: ["HS256"] });
|
|
6912
6946
|
}
|
|
6913
6947
|
function verifyClientToken(token, publicKeyB64, algorithm) {
|
|
6914
|
-
const
|
|
6915
|
-
const publicKeyObject = crypto2.createPublicKey({
|
|
6916
|
-
key: publicKeyDER,
|
|
6917
|
-
format: "der",
|
|
6918
|
-
type: "spki"
|
|
6919
|
-
});
|
|
6948
|
+
const publicKeyObject = getPublicKeyObject(publicKeyB64);
|
|
6920
6949
|
let decoded;
|
|
6921
6950
|
try {
|
|
6922
6951
|
decoded = jwt.verify(token, publicKeyObject, {
|
|
@@ -7002,6 +7031,7 @@ import {
|
|
|
7002
7031
|
} from "@spfn/auth/errors";
|
|
7003
7032
|
|
|
7004
7033
|
// src/server/services/verification.service.ts
|
|
7034
|
+
import crypto4 from "crypto";
|
|
7005
7035
|
import { env as env4 } from "@spfn/auth/config";
|
|
7006
7036
|
import { InvalidVerificationCodeError } from "@spfn/auth/errors";
|
|
7007
7037
|
import jwt2 from "jsonwebtoken";
|
|
@@ -7027,11 +7057,12 @@ var authLogger = {
|
|
|
7027
7057
|
|
|
7028
7058
|
// src/server/services/verification.service.ts
|
|
7029
7059
|
init_repositories();
|
|
7060
|
+
var ACCOUNT_EXISTS_NOTICE_DEDUPE_MINUTES = 60;
|
|
7030
7061
|
var VERIFICATION_TOKEN_EXPIRY = "15m";
|
|
7031
7062
|
var VERIFICATION_CODE_EXPIRY_MINUTES = 5;
|
|
7032
7063
|
var MAX_VERIFICATION_ATTEMPTS = 5;
|
|
7033
7064
|
function generateVerificationCode() {
|
|
7034
|
-
return
|
|
7065
|
+
return crypto4.randomInt(0, 1e6).toString().padStart(6, "0");
|
|
7035
7066
|
}
|
|
7036
7067
|
async function storeVerificationCode(target, targetType, code, purpose) {
|
|
7037
7068
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
@@ -7126,8 +7157,39 @@ async function sendVerificationSMS(phone, code, purpose) {
|
|
|
7126
7157
|
});
|
|
7127
7158
|
}
|
|
7128
7159
|
}
|
|
7160
|
+
async function accountExistsForTarget(target, targetType) {
|
|
7161
|
+
const user = targetType === "email" ? await usersRepository.findByEmail(target) : await usersRepository.findByPhone(target);
|
|
7162
|
+
return !!user;
|
|
7163
|
+
}
|
|
7164
|
+
async function sendAccountExistsNotice(target, targetType) {
|
|
7165
|
+
const result = targetType === "email" ? await sendEmail({ to: target, template: "account-exists", data: {} }) : await sendSMS({ to: target, template: "account-exists", data: {} });
|
|
7166
|
+
if (!result.success) {
|
|
7167
|
+
const log = targetType === "email" ? authLogger.email : authLogger.sms;
|
|
7168
|
+
log.error("Failed to send account-exists notice", { target, error: result.error });
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7129
7171
|
async function sendVerificationCodeService(params) {
|
|
7130
7172
|
const { target, targetType, purpose } = params;
|
|
7173
|
+
if (purpose === "registration" && await accountExistsForTarget(target, targetType)) {
|
|
7174
|
+
const recentNotice = await verificationCodesRepository.findValidByTargetAndPurpose(target, purpose);
|
|
7175
|
+
if (!recentNotice) {
|
|
7176
|
+
const dedupeExpiresAt = new Date(Date.now() + ACCOUNT_EXISTS_NOTICE_DEDUPE_MINUTES * 6e4);
|
|
7177
|
+
await verificationCodesRepository.invalidatePreviousCodes(target, purpose);
|
|
7178
|
+
await verificationCodesRepository.create({
|
|
7179
|
+
target,
|
|
7180
|
+
targetType,
|
|
7181
|
+
code: generateVerificationCode(),
|
|
7182
|
+
purpose,
|
|
7183
|
+
expiresAt: dedupeExpiresAt,
|
|
7184
|
+
attempts: 0
|
|
7185
|
+
});
|
|
7186
|
+
await sendAccountExistsNotice(target, targetType);
|
|
7187
|
+
}
|
|
7188
|
+
return {
|
|
7189
|
+
success: true,
|
|
7190
|
+
expiresAt: new Date(Date.now() + VERIFICATION_CODE_EXPIRY_MINUTES * 6e4).toISOString()
|
|
7191
|
+
};
|
|
7192
|
+
}
|
|
7131
7193
|
const code = generateVerificationCode();
|
|
7132
7194
|
const codeRecord = await storeVerificationCode(target, targetType, code, purpose);
|
|
7133
7195
|
if (targetType === "email") {
|
|
@@ -7344,27 +7406,12 @@ var invitationAcceptedEvent = defineEvent(
|
|
|
7344
7406
|
);
|
|
7345
7407
|
|
|
7346
7408
|
// src/server/services/auth.service.ts
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
let user;
|
|
7352
|
-
if (email) {
|
|
7353
|
-
identifier = email;
|
|
7354
|
-
identifierType = "email";
|
|
7355
|
-
user = await usersRepository.findByEmail(email);
|
|
7356
|
-
} else if (phone) {
|
|
7357
|
-
identifier = phone;
|
|
7358
|
-
identifierType = "phone";
|
|
7359
|
-
user = await usersRepository.findByPhone(phone);
|
|
7360
|
-
} else {
|
|
7361
|
-
throw new ValidationError2({ message: "Either email or phone must be provided" });
|
|
7409
|
+
var dummyHashPromise = null;
|
|
7410
|
+
function getDummyHash() {
|
|
7411
|
+
if (!dummyHashPromise) {
|
|
7412
|
+
dummyHashPromise = hashPassword("spfn-nonexistent-account-timing-equalizer");
|
|
7362
7413
|
}
|
|
7363
|
-
return
|
|
7364
|
-
exists: !!user,
|
|
7365
|
-
identifier,
|
|
7366
|
-
identifierType
|
|
7367
|
-
};
|
|
7414
|
+
return dummyHashPromise;
|
|
7368
7415
|
}
|
|
7369
7416
|
async function registerService(params) {
|
|
7370
7417
|
const { email, phone, verificationToken, password, publicKey, keyId, fingerprint, algorithm, metadata } = params;
|
|
@@ -7431,6 +7478,7 @@ async function loginService(params) {
|
|
|
7431
7478
|
throw new ValidationError2({ message: "Either email or phone must be provided" });
|
|
7432
7479
|
}
|
|
7433
7480
|
if (!user || !user.passwordHash) {
|
|
7481
|
+
await verifyPassword(password, await getDummyHash());
|
|
7434
7482
|
throw new InvalidCredentialsError();
|
|
7435
7483
|
}
|
|
7436
7484
|
const isValid = await verifyPassword(password, user.passwordHash);
|
|
@@ -7717,6 +7765,7 @@ async function syncMappings(allMappings, rolesByName, permsByName) {
|
|
|
7717
7765
|
|
|
7718
7766
|
// src/server/services/permission.service.ts
|
|
7719
7767
|
init_repositories();
|
|
7768
|
+
import { ForbiddenError } from "@spfn/core/errors";
|
|
7720
7769
|
async function getUserPermissions(userId) {
|
|
7721
7770
|
const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
|
|
7722
7771
|
const user = await usersRepository.findById(userIdNum);
|
|
@@ -7781,15 +7830,32 @@ async function hasAnyRole(userId, roleNames) {
|
|
|
7781
7830
|
}
|
|
7782
7831
|
return false;
|
|
7783
7832
|
}
|
|
7833
|
+
async function assertCanAssignRole(callerUserId, targetRoleId) {
|
|
7834
|
+
const callerRole = await getUserRole(callerUserId);
|
|
7835
|
+
if (callerRole === "superadmin") {
|
|
7836
|
+
return;
|
|
7837
|
+
}
|
|
7838
|
+
const targetRole = await rolesRepository.findById(targetRoleId);
|
|
7839
|
+
if (targetRole?.name === "superadmin") {
|
|
7840
|
+
throw new ForbiddenError({ message: "Only superadmin can assign superadmin role" });
|
|
7841
|
+
}
|
|
7842
|
+
if (targetRole?.name === "admin") {
|
|
7843
|
+
const canPromote = await hasPermission(callerUserId, "admin:promote");
|
|
7844
|
+
if (!canPromote) {
|
|
7845
|
+
throw new ForbiddenError({ message: "admin:promote permission required to assign admin role" });
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7784
7849
|
|
|
7785
7850
|
// src/server/services/index.ts
|
|
7786
7851
|
init_role_service();
|
|
7787
7852
|
|
|
7788
7853
|
// src/server/services/invitation.service.ts
|
|
7789
7854
|
init_repositories();
|
|
7790
|
-
import
|
|
7855
|
+
import crypto5 from "crypto";
|
|
7856
|
+
import { BadRequestError, NotFoundError as NotFoundError2, ConflictError } from "@spfn/core/errors";
|
|
7791
7857
|
function generateInvitationToken() {
|
|
7792
|
-
return
|
|
7858
|
+
return crypto5.randomUUID();
|
|
7793
7859
|
}
|
|
7794
7860
|
function calculateExpiresAt(days = 7) {
|
|
7795
7861
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
@@ -7800,23 +7866,23 @@ async function createInvitation(params) {
|
|
|
7800
7866
|
const { email, roleId, invitedBy, expiresInDays = 7, expiresAt: expiresAtParam, metadata } = params;
|
|
7801
7867
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7802
7868
|
if (!emailRegex.test(email)) {
|
|
7803
|
-
throw new
|
|
7869
|
+
throw new BadRequestError({ message: "Invalid email format" });
|
|
7804
7870
|
}
|
|
7805
7871
|
const existingUser = await usersRepository.findByEmail(email);
|
|
7806
7872
|
if (existingUser) {
|
|
7807
|
-
throw new
|
|
7873
|
+
throw new ConflictError({ message: "User with this email already exists" });
|
|
7808
7874
|
}
|
|
7809
7875
|
const existingInvitation = await invitationsRepository.findPendingByEmail(email);
|
|
7810
7876
|
if (existingInvitation) {
|
|
7811
|
-
throw new
|
|
7877
|
+
throw new ConflictError({ message: "Pending invitation already exists for this email" });
|
|
7812
7878
|
}
|
|
7813
7879
|
const role = await rolesRepository.findById(roleId);
|
|
7814
7880
|
if (!role) {
|
|
7815
|
-
throw new
|
|
7881
|
+
throw new NotFoundError2({ message: `Role with id ${roleId} not found`, resource: "Role" });
|
|
7816
7882
|
}
|
|
7817
7883
|
const inviter = await usersRepository.findById(invitedBy);
|
|
7818
7884
|
if (!inviter) {
|
|
7819
|
-
throw new
|
|
7885
|
+
throw new NotFoundError2({ message: `User with id ${invitedBy} not found`, resource: "User" });
|
|
7820
7886
|
}
|
|
7821
7887
|
const token = generateInvitationToken();
|
|
7822
7888
|
const expiresAt = expiresAtParam ?? calculateExpiresAt(expiresInDays);
|
|
@@ -7870,12 +7936,12 @@ async function acceptInvitation(params) {
|
|
|
7870
7936
|
const { token, password, publicKey, keyId, fingerprint, algorithm } = params;
|
|
7871
7937
|
const validation = await validateInvitation(token);
|
|
7872
7938
|
if (!validation.valid || !validation.invitation) {
|
|
7873
|
-
throw new
|
|
7939
|
+
throw new BadRequestError({ message: validation.error || "Invalid invitation" });
|
|
7874
7940
|
}
|
|
7875
7941
|
const invitation = validation.invitation;
|
|
7876
7942
|
const role = await rolesRepository.findById(invitation.roleId);
|
|
7877
7943
|
if (!role) {
|
|
7878
|
-
throw new
|
|
7944
|
+
throw new NotFoundError2({ message: "Role not found", resource: "Role" });
|
|
7879
7945
|
}
|
|
7880
7946
|
const passwordHash = await hashPassword(password);
|
|
7881
7947
|
const newUser = await usersRepository.create({
|
|
@@ -7922,10 +7988,10 @@ async function listInvitations(params) {
|
|
|
7922
7988
|
async function cancelInvitation(id11, cancelledBy, reason) {
|
|
7923
7989
|
const invitation = await invitationsRepository.findById(id11);
|
|
7924
7990
|
if (!invitation) {
|
|
7925
|
-
throw new
|
|
7991
|
+
throw new NotFoundError2({ message: "Invitation not found", resource: "Invitation" });
|
|
7926
7992
|
}
|
|
7927
7993
|
if (invitation.status !== "pending") {
|
|
7928
|
-
throw new
|
|
7994
|
+
throw new ConflictError({ message: `Cannot cancel ${invitation.status} invitation` });
|
|
7929
7995
|
}
|
|
7930
7996
|
await invitationsRepository.cancel(id11, cancelledBy, reason, invitation.metadata);
|
|
7931
7997
|
console.log(`[Auth] \u26A0\uFE0F Invitation cancelled: ${invitation.email} (reason: ${reason || "none"})`);
|
|
@@ -7944,10 +8010,10 @@ async function expireOldInvitations() {
|
|
|
7944
8010
|
async function resendInvitation(id11, expiresInDays = 7) {
|
|
7945
8011
|
const invitation = await invitationsRepository.findById(id11);
|
|
7946
8012
|
if (!invitation) {
|
|
7947
|
-
throw new
|
|
8013
|
+
throw new NotFoundError2({ message: "Invitation not found", resource: "Invitation" });
|
|
7948
8014
|
}
|
|
7949
8015
|
if (!["pending", "expired"].includes(invitation.status)) {
|
|
7950
|
-
throw new
|
|
8016
|
+
throw new ConflictError({ message: `Cannot resend ${invitation.status} invitation` });
|
|
7951
8017
|
}
|
|
7952
8018
|
const newExpiresAt = calculateExpiresAt(expiresInDays);
|
|
7953
8019
|
const updated = await invitationsRepository.resend(id11, newExpiresAt);
|
|
@@ -8668,16 +8734,8 @@ async function persistNativeLogin(identity, params) {
|
|
|
8668
8734
|
// src/server/routes/auth/index.ts
|
|
8669
8735
|
init_esm();
|
|
8670
8736
|
import { Transactional } from "@spfn/core/db";
|
|
8737
|
+
import { rateLimit } from "@spfn/core/middleware";
|
|
8671
8738
|
import { defineRouter, route } from "@spfn/core/route";
|
|
8672
|
-
var checkAccountExists = route.post("/_auth/exists").input({
|
|
8673
|
-
body: Type.Union([
|
|
8674
|
-
Type.Object({ email: EmailSchema }),
|
|
8675
|
-
Type.Object({ phone: PhoneSchema })
|
|
8676
|
-
])
|
|
8677
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
8678
|
-
const { body } = await c.data();
|
|
8679
|
-
return await checkAccountExistsService(body);
|
|
8680
|
-
});
|
|
8681
8739
|
var sendVerificationCode = route.post("/_auth/codes").input({
|
|
8682
8740
|
body: Type.Object({
|
|
8683
8741
|
target: Type.String({
|
|
@@ -8686,7 +8744,7 @@ var sendVerificationCode = route.post("/_auth/codes").input({
|
|
|
8686
8744
|
targetType: TargetTypeSchema,
|
|
8687
8745
|
purpose: VerificationPurposeSchema
|
|
8688
8746
|
})
|
|
8689
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
8747
|
+
}).use([rateLimit({ limit: 5, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
8690
8748
|
const { body } = await c.data();
|
|
8691
8749
|
return await sendVerificationCodeService(body);
|
|
8692
8750
|
});
|
|
@@ -8704,7 +8762,7 @@ var verifyCode = route.post("/_auth/codes/verify").input({
|
|
|
8704
8762
|
}),
|
|
8705
8763
|
purpose: VerificationPurposeSchema
|
|
8706
8764
|
})
|
|
8707
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
8765
|
+
}).use([rateLimit({ limit: 10, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
8708
8766
|
const { body } = await c.data();
|
|
8709
8767
|
return await verifyCodeService(body);
|
|
8710
8768
|
});
|
|
@@ -8756,7 +8814,7 @@ var login = route.post("/_auth/login").input({
|
|
|
8756
8814
|
algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" }),
|
|
8757
8815
|
oldKeyId: Type.Optional(Type.String({ description: "Previous key ID for rotation" }))
|
|
8758
8816
|
})
|
|
8759
|
-
}).use([Transactional()]).skip(["auth"]).handler(async (c) => {
|
|
8817
|
+
}).use([rateLimit({ limit: 10, windowMs: 6e4 }), Transactional()]).skip(["auth"]).handler(async (c) => {
|
|
8760
8818
|
const { body } = await c.data();
|
|
8761
8819
|
return await loginService(body);
|
|
8762
8820
|
});
|
|
@@ -8816,7 +8874,6 @@ var issueOneTimeToken = route.post("/_auth/tokens").handler(async (c) => {
|
|
|
8816
8874
|
return await issueOneTimeTokenService(userId);
|
|
8817
8875
|
});
|
|
8818
8876
|
var authRouter = defineRouter({
|
|
8819
|
-
checkAccountExists,
|
|
8820
8877
|
sendVerificationCode,
|
|
8821
8878
|
verifyCode,
|
|
8822
8879
|
register,
|
|
@@ -8965,7 +9022,7 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
|
|
|
8965
9022
|
|
|
8966
9023
|
// src/server/middleware/require-permission.ts
|
|
8967
9024
|
import { defineMiddleware as defineMiddleware2 } from "@spfn/core/route";
|
|
8968
|
-
import { ForbiddenError } from "@spfn/core/errors";
|
|
9025
|
+
import { ForbiddenError as ForbiddenError2 } from "@spfn/core/errors";
|
|
8969
9026
|
import { InsufficientPermissionsError } from "@spfn/auth/errors";
|
|
8970
9027
|
import { getAuth as getAuth2, hasAllPermissions as hasAllPermissions2, hasAnyPermission as hasAnyPermission2, authLogger as authLogger3 } from "@spfn/auth/server";
|
|
8971
9028
|
var requirePermissions = defineMiddleware2(
|
|
@@ -8977,7 +9034,7 @@ var requirePermissions = defineMiddleware2(
|
|
|
8977
9034
|
permissions: permissionNames,
|
|
8978
9035
|
path: c.req.path
|
|
8979
9036
|
});
|
|
8980
|
-
throw new
|
|
9037
|
+
throw new ForbiddenError2({ message: "Authentication required" });
|
|
8981
9038
|
}
|
|
8982
9039
|
const { userId } = auth;
|
|
8983
9040
|
const allowed = await hasAllPermissions2(userId, permissionNames);
|
|
@@ -9005,7 +9062,7 @@ var requireAnyPermission = defineMiddleware2(
|
|
|
9005
9062
|
permissions: permissionNames,
|
|
9006
9063
|
path: c.req.path
|
|
9007
9064
|
});
|
|
9008
|
-
throw new
|
|
9065
|
+
throw new ForbiddenError2({ message: "Authentication required" });
|
|
9009
9066
|
}
|
|
9010
9067
|
const { userId } = auth;
|
|
9011
9068
|
const allowed = await hasAnyPermission2(userId, permissionNames);
|
|
@@ -9028,7 +9085,7 @@ var requireAnyPermission = defineMiddleware2(
|
|
|
9028
9085
|
// src/server/middleware/require-role.ts
|
|
9029
9086
|
import { defineMiddleware as defineMiddleware3 } from "@spfn/core/route";
|
|
9030
9087
|
import { getAuth as getAuth3, authLogger as authLogger4 } from "@spfn/auth/server";
|
|
9031
|
-
import { ForbiddenError as
|
|
9088
|
+
import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
|
|
9032
9089
|
import { InsufficientRoleError } from "@spfn/auth/errors";
|
|
9033
9090
|
var requireRole = defineMiddleware3(
|
|
9034
9091
|
"role",
|
|
@@ -9039,7 +9096,7 @@ var requireRole = defineMiddleware3(
|
|
|
9039
9096
|
roles: roleNames,
|
|
9040
9097
|
path: c.req.path
|
|
9041
9098
|
});
|
|
9042
|
-
throw new
|
|
9099
|
+
throw new ForbiddenError3({ message: "Authentication required" });
|
|
9043
9100
|
}
|
|
9044
9101
|
const { userId, role: userRole } = auth;
|
|
9045
9102
|
if (!userRole || !roleNames.includes(userRole)) {
|
|
@@ -9063,7 +9120,7 @@ var requireRole = defineMiddleware3(
|
|
|
9063
9120
|
// src/server/middleware/role-guard.ts
|
|
9064
9121
|
import { defineMiddleware as defineMiddleware4 } from "@spfn/core/route";
|
|
9065
9122
|
import { getAuth as getAuth4, authLogger as authLogger5 } from "@spfn/auth/server";
|
|
9066
|
-
import { ForbiddenError as
|
|
9123
|
+
import { ForbiddenError as ForbiddenError4 } from "@spfn/core/errors";
|
|
9067
9124
|
import { InsufficientRoleError as InsufficientRoleError2 } from "@spfn/auth/errors";
|
|
9068
9125
|
var roleGuard = defineMiddleware4(
|
|
9069
9126
|
"roleGuard",
|
|
@@ -9077,7 +9134,7 @@ var roleGuard = defineMiddleware4(
|
|
|
9077
9134
|
authLogger5.middleware.warn("Role guard failed: not authenticated", {
|
|
9078
9135
|
path: c.req.path
|
|
9079
9136
|
});
|
|
9080
|
-
throw new
|
|
9137
|
+
throw new ForbiddenError4({ message: "Authentication required" });
|
|
9081
9138
|
}
|
|
9082
9139
|
const { userId, role: userRole } = auth;
|
|
9083
9140
|
if (deny && deny.length > 0) {
|
|
@@ -9156,6 +9213,7 @@ function extractOTTHeader(header) {
|
|
|
9156
9213
|
// src/server/routes/invitations/index.ts
|
|
9157
9214
|
init_types();
|
|
9158
9215
|
init_esm();
|
|
9216
|
+
import { Transactional as Transactional2 } from "@spfn/core/db";
|
|
9159
9217
|
import { defineRouter as defineRouter2, route as route2 } from "@spfn/core/route";
|
|
9160
9218
|
var INVITATION_STATUSES2 = ["pending", "accepted", "expired", "cancelled"];
|
|
9161
9219
|
var getInvitation = route2.get("/_auth/invitations/:token").input({
|
|
@@ -9203,7 +9261,7 @@ var acceptInvitation2 = route2.post("/_auth/invitations/accept").input({
|
|
|
9203
9261
|
fingerprint: Type.String({ description: "Key fingerprint" }),
|
|
9204
9262
|
algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" })
|
|
9205
9263
|
})
|
|
9206
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
9264
|
+
}).use([Transactional2()]).skip(["auth"]).handler(async (c) => {
|
|
9207
9265
|
const { body } = await c.data();
|
|
9208
9266
|
return await acceptInvitation({
|
|
9209
9267
|
token: body.token,
|
|
@@ -9239,6 +9297,7 @@ var createInvitation2 = route2.post("/_auth/invitations").input({
|
|
|
9239
9297
|
}).use([authenticate, requirePermissions("user:invite")]).handler(async (c) => {
|
|
9240
9298
|
const { body } = await c.data();
|
|
9241
9299
|
const { userId } = getAuth(c);
|
|
9300
|
+
await assertCanAssignRole(userId, body.roleId);
|
|
9242
9301
|
const invitation = await createInvitation({
|
|
9243
9302
|
email: body.email,
|
|
9244
9303
|
roleId: body.roleId,
|
|
@@ -9419,8 +9478,9 @@ var userRouter = defineRouter3({
|
|
|
9419
9478
|
// src/server/routes/oauth/index.ts
|
|
9420
9479
|
init_esm();
|
|
9421
9480
|
init_types();
|
|
9422
|
-
import { Transactional as
|
|
9481
|
+
import { Transactional as Transactional3 } from "@spfn/core/db";
|
|
9423
9482
|
import { ValidationError as ValidationError7 } from "@spfn/core/errors";
|
|
9483
|
+
import { rateLimit as rateLimit2 } from "@spfn/core/middleware";
|
|
9424
9484
|
import { defineRouter as defineRouter4, route as route4 } from "@spfn/core/route";
|
|
9425
9485
|
var providerParams = Type.Object({
|
|
9426
9486
|
provider: Type.Union(SOCIAL_PROVIDERS.map((p) => Type.Literal(p)), {
|
|
@@ -9433,7 +9493,7 @@ var oauthGoogleStart = route4.get("/_auth/oauth/google").input({
|
|
|
9433
9493
|
description: "Encrypted OAuth state (returnUrl, publicKey, keyId, fingerprint, algorithm)"
|
|
9434
9494
|
})
|
|
9435
9495
|
})
|
|
9436
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
9496
|
+
}).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
9437
9497
|
const { query } = await c.data();
|
|
9438
9498
|
if (!isGoogleOAuthEnabled()) {
|
|
9439
9499
|
return c.redirect(buildOAuthErrorUrl("Google OAuth is not configured"));
|
|
@@ -9456,7 +9516,7 @@ var oauthGoogleCallback = route4.get("/_auth/oauth/google/callback").input({
|
|
|
9456
9516
|
description: "Error description from Google"
|
|
9457
9517
|
}))
|
|
9458
9518
|
})
|
|
9459
|
-
}).use([
|
|
9519
|
+
}).use([Transactional3()]).skip(["auth"]).handler(async (c) => {
|
|
9460
9520
|
const { query } = await c.data();
|
|
9461
9521
|
if (query.error) {
|
|
9462
9522
|
const errorMessage = query.error_description || query.error;
|
|
@@ -9501,7 +9561,7 @@ var oauthStart = route4.post("/_auth/oauth/start").input({
|
|
|
9501
9561
|
description: "Custom metadata passed to authRegisterEvent (e.g. referral code, UTM params)"
|
|
9502
9562
|
}))
|
|
9503
9563
|
})
|
|
9504
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
9564
|
+
}).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
9505
9565
|
const { body } = await c.data();
|
|
9506
9566
|
const result = await oauthStartService(body);
|
|
9507
9567
|
return result;
|
|
@@ -9554,7 +9614,7 @@ var oauthProviderStart = route4.get("/_auth/oauth/:provider").input({
|
|
|
9554
9614
|
description: "Encrypted OAuth state (returnUrl, publicKey, keyId, fingerprint, algorithm)"
|
|
9555
9615
|
})
|
|
9556
9616
|
})
|
|
9557
|
-
}).skip(["auth"]).handler(async (c) => {
|
|
9617
|
+
}).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
9558
9618
|
const { params, query } = await c.data();
|
|
9559
9619
|
const provider = getOAuthProvider(params.provider);
|
|
9560
9620
|
if (!provider?.isEnabled()) {
|
|
@@ -9578,7 +9638,7 @@ var oauthProviderCallback = route4.get("/_auth/oauth/:provider/callback").input(
|
|
|
9578
9638
|
description: "Error description from provider"
|
|
9579
9639
|
}))
|
|
9580
9640
|
})
|
|
9581
|
-
}).use([
|
|
9641
|
+
}).use([Transactional3()]).skip(["auth"]).handler(async (c) => {
|
|
9582
9642
|
const { params, query } = await c.data();
|
|
9583
9643
|
if (query.error) {
|
|
9584
9644
|
const errorMessage = query.error_description || query.error;
|
|
@@ -9657,9 +9717,8 @@ var oauthRouter = defineRouter4({
|
|
|
9657
9717
|
});
|
|
9658
9718
|
|
|
9659
9719
|
// src/server/routes/admin/index.ts
|
|
9660
|
-
init_repositories();
|
|
9661
9720
|
init_esm();
|
|
9662
|
-
import { ForbiddenError as
|
|
9721
|
+
import { ForbiddenError as ForbiddenError5 } from "@spfn/core/errors";
|
|
9663
9722
|
import { route as route5 } from "@spfn/core/route";
|
|
9664
9723
|
var listRoles = route5.get("/_auth/admin/roles").input({
|
|
9665
9724
|
query: Type.Object({
|
|
@@ -9728,26 +9787,14 @@ var updateUserRole = route5.patch("/_auth/admin/users/:userId/role").input({
|
|
|
9728
9787
|
}).use([authenticate, requireRole("admin", "superadmin")]).handler(async (c) => {
|
|
9729
9788
|
const { params, body } = await c.data();
|
|
9730
9789
|
const auth = getAuth(c);
|
|
9731
|
-
const callerRole = await getUserRole(auth.userId);
|
|
9732
9790
|
if (params.userId === Number(auth.userId)) {
|
|
9733
|
-
throw new
|
|
9791
|
+
throw new ForbiddenError5({ message: "Cannot change your own role" });
|
|
9734
9792
|
}
|
|
9735
9793
|
const targetRole = await getUserRole(params.userId);
|
|
9736
9794
|
if (targetRole === "superadmin") {
|
|
9737
|
-
throw new
|
|
9738
|
-
}
|
|
9739
|
-
if (callerRole !== "superadmin") {
|
|
9740
|
-
const newRole = await rolesRepository.findById(body.roleId);
|
|
9741
|
-
if (newRole?.name === "superadmin") {
|
|
9742
|
-
throw new ForbiddenError4({ message: "Only superadmin can assign superadmin role" });
|
|
9743
|
-
}
|
|
9744
|
-
if (newRole?.name === "admin") {
|
|
9745
|
-
const canPromote = await hasPermission(auth.userId, "admin:promote");
|
|
9746
|
-
if (!canPromote) {
|
|
9747
|
-
throw new ForbiddenError4({ message: "admin:promote permission required to assign admin role" });
|
|
9748
|
-
}
|
|
9749
|
-
}
|
|
9795
|
+
throw new ForbiddenError5({ message: "Cannot modify superadmin role" });
|
|
9750
9796
|
}
|
|
9797
|
+
await assertCanAssignRole(auth.userId, body.roleId);
|
|
9751
9798
|
await updateUserService(params.userId, { roleId: body.roleId });
|
|
9752
9799
|
return { userId: params.userId, roleId: body.roleId };
|
|
9753
9800
|
});
|
|
@@ -9755,7 +9802,6 @@ var updateUserRole = route5.patch("/_auth/admin/users/:userId/role").input({
|
|
|
9755
9802
|
// src/server/routes/index.ts
|
|
9756
9803
|
var mainAuthRouter = defineRouter5({
|
|
9757
9804
|
// Auth routes
|
|
9758
|
-
checkAccountExists,
|
|
9759
9805
|
sendVerificationCode,
|
|
9760
9806
|
verifyCode,
|
|
9761
9807
|
register,
|
|
@@ -9806,11 +9852,11 @@ init_types();
|
|
|
9806
9852
|
init_schema3();
|
|
9807
9853
|
|
|
9808
9854
|
// src/server/lib/crypto.ts
|
|
9809
|
-
import
|
|
9855
|
+
import crypto6 from "crypto";
|
|
9810
9856
|
import jwt3 from "jsonwebtoken";
|
|
9811
9857
|
function generateKeyPairES256() {
|
|
9812
|
-
const keyId =
|
|
9813
|
-
const { privateKey, publicKey } =
|
|
9858
|
+
const keyId = crypto6.randomUUID();
|
|
9859
|
+
const { privateKey, publicKey } = crypto6.generateKeyPairSync("ec", {
|
|
9814
9860
|
namedCurve: "P-256",
|
|
9815
9861
|
// ES256
|
|
9816
9862
|
publicKeyEncoding: {
|
|
@@ -9824,7 +9870,7 @@ function generateKeyPairES256() {
|
|
|
9824
9870
|
});
|
|
9825
9871
|
const privateKeyB64 = privateKey.toString("base64");
|
|
9826
9872
|
const publicKeyB64 = publicKey.toString("base64");
|
|
9827
|
-
const fingerprint =
|
|
9873
|
+
const fingerprint = crypto6.createHash("sha256").update(publicKey).digest("hex");
|
|
9828
9874
|
return {
|
|
9829
9875
|
privateKey: privateKeyB64,
|
|
9830
9876
|
publicKey: publicKeyB64,
|
|
@@ -9834,8 +9880,8 @@ function generateKeyPairES256() {
|
|
|
9834
9880
|
};
|
|
9835
9881
|
}
|
|
9836
9882
|
function generateKeyPairRS256() {
|
|
9837
|
-
const keyId =
|
|
9838
|
-
const { privateKey, publicKey } =
|
|
9883
|
+
const keyId = crypto6.randomUUID();
|
|
9884
|
+
const { privateKey, publicKey } = crypto6.generateKeyPairSync("rsa", {
|
|
9839
9885
|
modulusLength: 2048,
|
|
9840
9886
|
publicKeyEncoding: {
|
|
9841
9887
|
type: "spki",
|
|
@@ -9848,7 +9894,7 @@ function generateKeyPairRS256() {
|
|
|
9848
9894
|
});
|
|
9849
9895
|
const privateKeyB64 = privateKey.toString("base64");
|
|
9850
9896
|
const publicKeyB64 = publicKey.toString("base64");
|
|
9851
|
-
const fingerprint =
|
|
9897
|
+
const fingerprint = crypto6.createHash("sha256").update(publicKey).digest("hex");
|
|
9852
9898
|
return {
|
|
9853
9899
|
privateKey: privateKeyB64,
|
|
9854
9900
|
publicKey: publicKeyB64,
|
|
@@ -9863,7 +9909,7 @@ function generateKeyPair(algorithm = "ES256") {
|
|
|
9863
9909
|
function generateClientToken(payload, privateKeyB64, algorithm, options) {
|
|
9864
9910
|
try {
|
|
9865
9911
|
const privateKeyDER = Buffer.from(privateKeyB64, "base64");
|
|
9866
|
-
const privateKeyObject =
|
|
9912
|
+
const privateKeyObject = crypto6.createPrivateKey({
|
|
9867
9913
|
key: privateKeyDER,
|
|
9868
9914
|
format: "der",
|
|
9869
9915
|
type: "pkcs8"
|
|
@@ -9918,8 +9964,8 @@ async function getSessionSecretKey() {
|
|
|
9918
9964
|
}
|
|
9919
9965
|
async function getSecretFingerprint() {
|
|
9920
9966
|
const key = await getSessionSecretKey();
|
|
9921
|
-
const
|
|
9922
|
-
const hex = Array.from(new Uint8Array(
|
|
9967
|
+
const hash2 = await crypto.subtle.digest("SHA-256", key.buffer);
|
|
9968
|
+
const hex = Array.from(new Uint8Array(hash2)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9923
9969
|
return hex.slice(0, 8);
|
|
9924
9970
|
}
|
|
9925
9971
|
async function sealSession(data, ttl = 60 * 60 * 24 * 7) {
|
|
@@ -10160,6 +10206,7 @@ export {
|
|
|
10160
10206
|
acceptInvitation,
|
|
10161
10207
|
addPermissionToRole,
|
|
10162
10208
|
appleProvider,
|
|
10209
|
+
assertCanAssignRole,
|
|
10163
10210
|
authLogger,
|
|
10164
10211
|
authLoginEvent,
|
|
10165
10212
|
authMetadata,
|
|
@@ -10171,7 +10218,6 @@ export {
|
|
|
10171
10218
|
buildOAuthErrorUrl,
|
|
10172
10219
|
cancelInvitation,
|
|
10173
10220
|
changePasswordService,
|
|
10174
|
-
checkAccountExistsService,
|
|
10175
10221
|
checkUsernameAvailableService,
|
|
10176
10222
|
configureAuth,
|
|
10177
10223
|
createAuthLifecycle,
|