refactor(crypto): make system-wrapped DEKs the authoritative key path

DataCrypto and AuthManager now read keys through UserKeyManager: DEKs are
always unwrappable server-side, so the in-memory unlock session, DEK-in-JWT
wrapping, session-expiry data locks and ALLOW_APIKEY_DATA_UNLOCK are gone.
utils/user-crypto.ts is deleted; boot migration now cleans legacy wraps.
A one-release shim adopts DEKs from legacy dataKeyWrap tokens so active
password users migrate without re-login. Password login migrates legacy
password-wrapped DEKs via migratePasswordUserAtLogin.
This commit is contained in:
LukeGus
2026-07-16 00:19:12 -05:00
parent 16506de9da
commit ee7768dd86
14 changed files with 234 additions and 1114 deletions
@@ -49,10 +49,6 @@ vi.mock("../../utils/data-crypto.js", () => ({
DataCrypto: { getInstance: () => ({ encrypt: vi.fn(), decrypt: vi.fn() }) },
}));
vi.mock("../../utils/user-crypto.js", () => ({
UserCrypto: { getInstance: () => ({ getUserKey: vi.fn() }) },
}));
vi.mock("../repositories/factory.js", () => ({
createCurrentTermixIdentityCaRepository: vi.fn(() => ({
findPublicByIdentityId: vi.fn(),
@@ -119,40 +119,6 @@ export function registerUserOidcAccountRoutes(
scopes: oidcUser.scopes || "openid email profile",
});
try {
await authManager.convertToOIDCEncryption(targetUser.id);
} catch (encryptionError) {
authLogger.error(
"Failed to convert encryption to OIDC during linking",
encryptionError,
{
operation: "link_convert_encryption_failed",
userId: targetUser.id,
},
);
await userRepository.update(targetUser.id, {
isOidc: false,
oidcIdentifier: null,
clientId: "",
clientSecret: "",
issuerUrl: "",
authorizationUrl: "",
tokenUrl: "",
identifierPath: "",
namePath: "",
scopes: "openid email profile",
});
return res.status(500).json({
error:
"Failed to convert encryption for dual-auth. Please ensure the password account has encryption setup.",
details:
encryptionError instanceof Error
? encryptionError.message
: "Unknown error",
});
}
await authManager.revokeAllUserSessions(oidcUserId);
authManager.logoutUser(oidcUserId);
@@ -251,8 +251,6 @@ export function registerUserWebAuthnRoutes(
(req.body?.response as RegistrationResponseJSON | undefined)?.response
?.transports ?? [];
await authManager.setupWebAuthnUserEncryption(userId);
const name =
typeof req.body?.name === "string" && req.body.name.trim()
? req.body.name.trim().slice(0, 80)
-12
View File
@@ -1508,18 +1508,6 @@ router.post("/login", async (req, res) => {
return res.status(401).json({ error: "Invalid username or password" });
}
try {
const kekSalt = await createCurrentSettingsRepository().get(
`user_kek_salt_${userRecord.id}`,
);
if (!kekSalt) {
await authManager.registerUser(userRecord.id, password);
}
} catch {
// expected - KEK salt registration may fail for existing users
}
const deviceInfo = parseUserAgent(req);
let dataUnlocked = false;