@luckystack/login 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/{chunk-CLYMWF3A.js → chunk-ML3OQ6R4.js} +29 -8
- package/dist/chunk-ML3OQ6R4.js.map +1 -0
- package/dist/{chunk-6IGNAMXW.js → chunk-S7TST3X6.js} +25 -5
- package/dist/chunk-S7TST3X6.js.map +1 -0
- package/dist/emailChange.d.ts +6 -0
- package/dist/forgotPassword.d.ts +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +64 -29
- package/dist/index.js.map +1 -1
- package/dist/logout-PO7TJ442.js +7 -0
- package/dist/oauthProviders.d.ts +38 -4
- package/dist/passwordReset.d.ts +9 -2
- package/dist/register.js +1 -1
- package/dist/session.d.ts +5 -3
- package/dist/sessionAdapter.d.ts +9 -0
- package/docs/oauth-providers.md +2 -0
- package/docs/password-reset.md +1 -1
- package/package.json +3 -3
- package/dist/chunk-6IGNAMXW.js.map +0 -1
- package/dist/chunk-CLYMWF3A.js.map +0 -1
- package/dist/logout-RD727I3V.js +0 -7
- /package/dist/{logout-RD727I3V.js.map → logout-PO7TJ442.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
registerOAuthProviderFactory,
|
|
15
15
|
registerOAuthProviders,
|
|
16
16
|
registerUserAdapter
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ML3OQ6R4.js";
|
|
18
18
|
import {
|
|
19
19
|
activeUsersKeyFor,
|
|
20
20
|
deleteSession,
|
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
revokeUserSessions,
|
|
30
30
|
saveSession,
|
|
31
31
|
sessionKeyFor
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-S7TST3X6.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
|
-
import { registerSessionProvider } from "@luckystack/core";
|
|
35
|
+
import { registerRedactedLogKeys, registerSessionProvider } from "@luckystack/core";
|
|
36
36
|
|
|
37
37
|
// src/authLockout.ts
|
|
38
38
|
import {
|
|
@@ -865,7 +865,7 @@ var fetchOAuthProfile = async (provider, accessToken) => {
|
|
|
865
865
|
const userData = asRecord(response);
|
|
866
866
|
const resolvedName = readStringField(userData, provider.nameKey);
|
|
867
867
|
const name = resolvedName === "" ? "didnt find a name" : resolvedName;
|
|
868
|
-
const emailValue = userData[provider.emailKey];
|
|
868
|
+
const emailValue = provider.alwaysUseGetEmail ? void 0 : userData[provider.emailKey];
|
|
869
869
|
let email = typeof emailValue === "string" ? emailValue : void 0;
|
|
870
870
|
let emailVerified = false;
|
|
871
871
|
if (email && provider.emailVerifiedKey) {
|
|
@@ -883,13 +883,14 @@ var fetchOAuthProfile = async (provider, accessToken) => {
|
|
|
883
883
|
const avatarValue = provider.avatarKey ? readStringField(userData, provider.avatarKey) : provider.getAvatar ? await provider.getAvatar({ userData, avatarId: typeof avatarId === "string" ? avatarId : "", accessToken }) : "";
|
|
884
884
|
const avatar = typeof avatarValue === "string" ? avatarValue : "";
|
|
885
885
|
if (!email && provider.getEmail) {
|
|
886
|
-
const
|
|
886
|
+
const selected = await provider.getEmail(accessToken, userData);
|
|
887
|
+
const selectedEmail = typeof selected === "object" ? selected.email : selected;
|
|
887
888
|
if (!selectedEmail) {
|
|
888
889
|
getLogger3().warn("oauth: no email found via provider.getEmail", { provider: provider.name });
|
|
889
890
|
return null;
|
|
890
891
|
}
|
|
891
892
|
email = selectedEmail;
|
|
892
|
-
emailVerified = true;
|
|
893
|
+
emailVerified = typeof selected === "object" ? selected.verified : true;
|
|
893
894
|
}
|
|
894
895
|
if (email !== void 0) {
|
|
895
896
|
const normalizedEmail = email.trim().toLowerCase();
|
|
@@ -1137,6 +1138,14 @@ var createPasswordResetToken = async (userId) => {
|
|
|
1137
1138
|
var consumePasswordResetToken = async (token) => {
|
|
1138
1139
|
return consumeOneTimeToken(PWRESET_NAMESPACE, token);
|
|
1139
1140
|
};
|
|
1141
|
+
var invalidatePasswordResetToken = async (userId) => {
|
|
1142
|
+
const pointerKey = pwresetUserKey(userId);
|
|
1143
|
+
const priorKey = await redis.get(pointerKey);
|
|
1144
|
+
if (priorKey) {
|
|
1145
|
+
await redis.del(priorKey);
|
|
1146
|
+
}
|
|
1147
|
+
await redis.del(pointerKey);
|
|
1148
|
+
};
|
|
1140
1149
|
var updatePasswordHash = async (userId, plaintext) => {
|
|
1141
1150
|
const reason = validatePassword(plaintext);
|
|
1142
1151
|
if (reason) {
|
|
@@ -1196,28 +1205,34 @@ var sendPasswordResetEmail = async ({ email, brand }) => {
|
|
|
1196
1205
|
});
|
|
1197
1206
|
return { ok: true };
|
|
1198
1207
|
}
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1208
|
+
const deliverResetEmail = async () => {
|
|
1209
|
+
const token = await createPasswordResetToken(user.id);
|
|
1210
|
+
void dispatchHook2("passwordResetRequested", {
|
|
1211
|
+
email,
|
|
1212
|
+
matched: true,
|
|
1213
|
+
userId: user.id,
|
|
1214
|
+
ttlSeconds: config.auth.passwordResetTtlSeconds
|
|
1215
|
+
});
|
|
1216
|
+
const baseUrl = (config.app.publicUrl || "").replace(/\/+$/, "");
|
|
1217
|
+
const resetPath = config.auth.passwordResetPath || "/reset-password";
|
|
1218
|
+
const resetUrl = `${baseUrl}${resetPath}?token=${encodeURIComponent(token)}`;
|
|
1219
|
+
const ttlMinutes = Math.round(config.auth.passwordResetTtlSeconds / 60);
|
|
1220
|
+
const result = await sendEmail({
|
|
1221
|
+
to: user.email,
|
|
1222
|
+
template: "password-reset",
|
|
1223
|
+
data: { resetUrl, userName: user.name, brand: resolvedBrand, ttlMinutes },
|
|
1224
|
+
adapterHint: "transactional"
|
|
1225
|
+
});
|
|
1226
|
+
if (result.ok) {
|
|
1227
|
+
getLogger4().info("[forgotPassword] reset email dispatched", { userId: user.id });
|
|
1228
|
+
} else {
|
|
1229
|
+
getLogger4().warn("[forgotPassword] reset email send FAILED", { userId: user.id, reason: result.reason });
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
void deliverResetEmail().catch((error) => {
|
|
1233
|
+
getLogger4().error("[forgotPassword] reset email delivery failed", { userId: user.id, error: String(error) });
|
|
1214
1234
|
});
|
|
1215
|
-
|
|
1216
|
-
getLogger4().info("[forgotPassword] reset email dispatched", { userId: user.id });
|
|
1217
|
-
} else {
|
|
1218
|
-
getLogger4().warn("[forgotPassword] reset email send FAILED", { userId: user.id, reason: result.reason });
|
|
1219
|
-
}
|
|
1220
|
-
return result.ok ? { ok: true } : { ok: false, reason: result.reason };
|
|
1235
|
+
return { ok: true };
|
|
1221
1236
|
};
|
|
1222
1237
|
|
|
1223
1238
|
// src/emailChange.ts
|
|
@@ -1247,6 +1262,14 @@ var consumeEmailChangeToken = async (token) => {
|
|
|
1247
1262
|
}
|
|
1248
1263
|
return parsed;
|
|
1249
1264
|
};
|
|
1265
|
+
var invalidateEmailChangeToken = async (userId) => {
|
|
1266
|
+
const pointerKey = emailChangeUserKey(userId);
|
|
1267
|
+
const priorKey = await redis2.get(pointerKey);
|
|
1268
|
+
if (priorKey) {
|
|
1269
|
+
await redis2.del(priorKey);
|
|
1270
|
+
}
|
|
1271
|
+
await redis2.del(pointerKey);
|
|
1272
|
+
};
|
|
1250
1273
|
|
|
1251
1274
|
// src/emailChangeNotification.ts
|
|
1252
1275
|
import { getProjectConfig as getProjectConfig8 } from "@luckystack/core";
|
|
@@ -1770,9 +1793,13 @@ var confirmTotpEnrollment = async (user, code) => {
|
|
|
1770
1793
|
}
|
|
1771
1794
|
};
|
|
1772
1795
|
var disableTwoFactor = async (user, code) => {
|
|
1796
|
+
if (await isTwoFactorAccountLocked(user.id)) return { ok: false, reason: "login.twoFactorLocked" };
|
|
1773
1797
|
const viaTotp = await verifyTotpForUser(user, code, false);
|
|
1774
1798
|
const viaRecovery = viaTotp ? false : await consumeRecoveryCode(user, code);
|
|
1775
|
-
if (!viaTotp && !viaRecovery)
|
|
1799
|
+
if (!viaTotp && !viaRecovery) {
|
|
1800
|
+
await recordTwoFactorAccountFailure(user.id);
|
|
1801
|
+
return { ok: false, reason: "login.twoFactorInvalidCode" };
|
|
1802
|
+
}
|
|
1776
1803
|
const [updateError] = await tryCatch3(() => getUserAdapter().update(user.id, {
|
|
1777
1804
|
totpSecret: null,
|
|
1778
1805
|
twoFactorEnabled: false,
|
|
@@ -1786,7 +1813,12 @@ var disableTwoFactor = async (user, code) => {
|
|
|
1786
1813
|
return { ok: true };
|
|
1787
1814
|
};
|
|
1788
1815
|
var regenerateRecoveryCodes = async (user, code) => {
|
|
1789
|
-
if (
|
|
1816
|
+
if (await isTwoFactorAccountLocked(user.id)) return { ok: false, reason: "login.twoFactorLocked" };
|
|
1817
|
+
if (!await verifyTotpForUser(user, code, false)) {
|
|
1818
|
+
await recordTwoFactorAccountFailure(user.id);
|
|
1819
|
+
return { ok: false, reason: "login.twoFactorInvalidCode" };
|
|
1820
|
+
}
|
|
1821
|
+
await clearTwoFactorAccountFailures(user.id);
|
|
1790
1822
|
const { raw, hashes } = generateRecoveryCodes();
|
|
1791
1823
|
const [updateError] = await tryCatch3(() => getUserAdapter().update(user.id, { recoveryCodes: hashes }));
|
|
1792
1824
|
if (updateError) {
|
|
@@ -1879,6 +1911,7 @@ registerSessionProvider({
|
|
|
1879
1911
|
logout: ({ token, socket, userId, skipSessionDelete }) => logout({ token, socket, userId: userId ?? null, skipSessionDelete })
|
|
1880
1912
|
});
|
|
1881
1913
|
registerAuthLockoutHook();
|
|
1914
|
+
registerRedactedLogKeys(["recoverycode", "totp", "totpcode"]);
|
|
1882
1915
|
export {
|
|
1883
1916
|
OAUTH_STATE_COOKIE_NAME,
|
|
1884
1917
|
PasswordPolicyError,
|
|
@@ -1917,6 +1950,8 @@ export {
|
|
|
1917
1950
|
githubProvider,
|
|
1918
1951
|
googleProvider,
|
|
1919
1952
|
hotp,
|
|
1953
|
+
invalidateEmailChangeToken,
|
|
1954
|
+
invalidatePasswordResetToken,
|
|
1920
1955
|
isAccountLocked,
|
|
1921
1956
|
isFullOAuthProvider,
|
|
1922
1957
|
isUserAdapterRegistered,
|