@fonderie/auth 7.5.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brain/signatures.md +2 -2
- package/dist/index.cjs +66 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +66 -3
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +30 -3
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.js +30 -3
- package/dist/middlewares/index.js.map +1 -1
- package/dist/{session-Dwlcgxww.d.cts → session-Bv4qu9xV.d.cts} +1 -0
- package/dist/{session-Dwlcgxww.d.ts → session-Bv4qu9xV.d.ts} +1 -0
- package/package.json +1 -1
package/brain/signatures.md
CHANGED
|
@@ -100,11 +100,11 @@ interface IDataExportContributor {
|
|
|
100
100
|
|
|
101
101
|
const AUTH_CONFIG_KEYS: { sessionDuration: string; verificationCooldown: string; mfa: string; requireVerification: string; }
|
|
102
102
|
|
|
103
|
-
const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; readonly oauthRegistration: "oauth-registration"; readonly oauthLinked: "oauth-linked"; readonly oauthUnlinked: "oauth-unlinked"; }
|
|
103
|
+
const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; readonly passwordRevoked: "password-revoked"; readonly oauthRegistration: "oauth-registration"; readonly oauthLinked: "oauth-linked"; readonly oauthUnlinked: "oauth-unlinked"; }
|
|
104
104
|
|
|
105
105
|
type AuthMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
|
|
106
106
|
|
|
107
|
-
const DEFAULT_TEMPLATES: { "email-registration": { subject: string; html: string; text: string; }; "email-verification": { subject: string; html: string; text: string; }; "password-reset": { subject: string; html: string; text: string; }; "phone-otp": { text: string; }; "mfa-enabled": { subject: string; html: string; text: string; }; "mfa-disabled": { subject: string; html: string; text: string; }; "mfa-backup-codes-regenerated": { subject: string; html: string; text: string; }; "email-changed": { subject: string; html: string; text: string; }; "phone-changed": { subject: string; html: string; text: string; }; "oauth-registration": { subject: string; html: string; text: string; }; "oauth-linked": { subject: string; html: string; text: string; }; "oauth-unlinked": { subject: string; html: string; text: string; }; }
|
|
107
|
+
const DEFAULT_TEMPLATES: { "email-registration": { subject: string; html: string; text: string; }; "email-verification": { subject: string; html: string; text: string; }; "password-reset": { subject: string; html: string; text: string; }; "phone-otp": { text: string; }; "mfa-enabled": { subject: string; html: string; text: string; }; "mfa-disabled": { subject: string; html: string; text: string; }; "mfa-backup-codes-regenerated": { subject: string; html: string; text: string; }; "email-changed": { subject: string; html: string; text: string; }; "phone-changed": { subject: string; html: string; text: string; }; "password-revoked": { subject: string; html: string; text: string; }; "oauth-registration": { subject: string; html: string; text: string; }; "oauth-linked": { subject: string; html: string; text: string; }; "oauth-unlinked": { subject: string; html: string; text: string; }; }
|
|
108
108
|
|
|
109
109
|
interface IUserDTO {
|
|
110
110
|
id: string;
|
package/dist/index.cjs
CHANGED
|
@@ -300,6 +300,11 @@ var MESSAGE_KEYS = {
|
|
|
300
300
|
// Changing HOW an account can be signed into is a security event for the
|
|
301
301
|
// account's owner, who may not be the person doing it. Both directions are
|
|
302
302
|
// notified for the same reason password changes are.
|
|
303
|
+
// A password was REVOKED because the account it sat on had never been
|
|
304
|
+
// verified and an OAuth identity proved ownership of the mailbox. The
|
|
305
|
+
// recipient is now known to own that address, so this reaches the right
|
|
306
|
+
// person — and it is the only way they learn a password stopped working.
|
|
307
|
+
passwordRevoked: "password-revoked",
|
|
303
308
|
// Welcome for an account CREATED by an OAuth sign-in. Distinct from
|
|
304
309
|
// emailRegistration, which carries a verification PIN — an OAuth signup
|
|
305
310
|
// arrives already verified by the provider and has nothing to confirm.
|
|
@@ -790,22 +795,49 @@ var UserModel = class {
|
|
|
790
795
|
* The prior state is read in a CTE of the SAME statement, so it sees the
|
|
791
796
|
* snapshot from before the insert. Reading it in a separate query would
|
|
792
797
|
* race two concurrent logins into both seeing "new".
|
|
798
|
+
*
|
|
799
|
+
* SECURITY — linking is BY EMAIL, so this merges into whatever row already
|
|
800
|
+
* holds the address. If that row was never verified, its password was never
|
|
801
|
+
* proven to belong to the mailbox; anyone can register an address they do
|
|
802
|
+
* not own. The provider HAS proven ownership. So an unverified password is
|
|
803
|
+
* revoked at the moment of linking: without that, linking sets
|
|
804
|
+
* email_verified_at and the earlier registrant is left holding a working
|
|
805
|
+
* password on an account now treated as verified.
|
|
806
|
+
*
|
|
807
|
+
* The legitimate user loses nothing but a password reset — they own the
|
|
808
|
+
* mailbox. Someone who does not own it cannot receive that mail.
|
|
793
809
|
*/
|
|
794
810
|
async upsertByProvider(email2, provider, providerId) {
|
|
795
811
|
const [row] = await this.store.query(
|
|
796
812
|
`WITH prior AS (
|
|
797
|
-
SELECT provider
|
|
813
|
+
SELECT provider, email_verified_at, password_hash IS NOT NULL AS had_password
|
|
814
|
+
FROM fonderie_users WHERE email = $1
|
|
798
815
|
), upserted AS (
|
|
799
816
|
INSERT INTO fonderie_users (email, email_verified_at, provider, provider_id)
|
|
800
817
|
VALUES ($1, now(), $2, $3)
|
|
801
818
|
ON CONFLICT (email) DO UPDATE
|
|
802
819
|
SET provider = $2, provider_id = $3,
|
|
803
|
-
email_verified_at = COALESCE(fonderie_users.email_verified_at, now())
|
|
820
|
+
email_verified_at = COALESCE(fonderie_users.email_verified_at, now()),
|
|
821
|
+
-- A password on an UNVERIFIED account is a claim, not a
|
|
822
|
+
-- credential: nobody ever proved that mailbox belongs to
|
|
823
|
+
-- whoever set it. The provider has now proven it belongs to
|
|
824
|
+
-- the person signing in. Drop the unproven claim rather than
|
|
825
|
+
-- promote it \u2014 otherwise linking marks the account verified
|
|
826
|
+
-- and silently hands the earlier registrant a working
|
|
827
|
+
-- password on a now-trusted account.
|
|
828
|
+
password_hash = CASE
|
|
829
|
+
WHEN fonderie_users.email_verified_at IS NULL THEN NULL
|
|
830
|
+
ELSE fonderie_users.password_hash
|
|
831
|
+
END
|
|
804
832
|
RETURNING id
|
|
805
833
|
)
|
|
806
834
|
SELECT upserted.id,
|
|
807
835
|
NOT EXISTS (SELECT 1 FROM prior) AS inserted,
|
|
808
|
-
(SELECT provider FROM prior) AS "previousProvider"
|
|
836
|
+
(SELECT provider FROM prior) AS "previousProvider",
|
|
837
|
+
-- True only when a password was actually revoked, so the
|
|
838
|
+
-- caller can tell the owner their sign-in method changed.
|
|
839
|
+
COALESCE((SELECT had_password AND email_verified_at IS NULL FROM prior), false)
|
|
840
|
+
AS "clearedUnverifiedPassword"
|
|
809
841
|
FROM upserted`,
|
|
810
842
|
[email2, provider, providerId]
|
|
811
843
|
);
|
|
@@ -2515,6 +2547,18 @@ function oauthController(store, config, bus) {
|
|
|
2515
2547
|
const reqOpts = reqId !== void 0 ? { requestId: reqId } : void 0;
|
|
2516
2548
|
const label = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2517
2549
|
const recipient = { email: user.email, phone: null, deviceToken: null };
|
|
2550
|
+
if (upserted.clearedUnverifiedPassword && user.email) {
|
|
2551
|
+
await (0, import_core9.background)(bus.emit(
|
|
2552
|
+
import_events4.NOTIFICATION_EVENT,
|
|
2553
|
+
{
|
|
2554
|
+
type: MESSAGE_KEYS.passwordRevoked,
|
|
2555
|
+
locale: user.locale,
|
|
2556
|
+
data: { provider: label },
|
|
2557
|
+
recipient
|
|
2558
|
+
},
|
|
2559
|
+
reqOpts
|
|
2560
|
+
));
|
|
2561
|
+
}
|
|
2518
2562
|
if (upserted.inserted) {
|
|
2519
2563
|
await (0, import_core9.background)(bus.emit(
|
|
2520
2564
|
EVENT_KEYS.userRegistered,
|
|
@@ -3271,6 +3315,24 @@ If you made this change, you're all set. If not, contact support right away \u20
|
|
|
3271
3315
|
The phone number on your account was just updated.
|
|
3272
3316
|
|
|
3273
3317
|
If you made this change, you're all set. If not, contact support right away \u2014 someone may have access to your account.`
|
|
3318
|
+
},
|
|
3319
|
+
[MESSAGE_KEYS.passwordRevoked]: {
|
|
3320
|
+
subject: "The password on your account was removed",
|
|
3321
|
+
html: `<h1>Your password was removed</h1>
|
|
3322
|
+
<p>You just signed in with <strong>{{provider}}</strong>. A password had been set on this account, but this email address had never been confirmed — so we could not tell who set it, and it has been removed.</p>
|
|
3323
|
+
<p>Signing in with {{provider}} works as before. If you want a password as well, set one from your account settings.</p>
|
|
3324
|
+
<p class="muted">If you never set a password here, someone else may have tried to register with your address. Nothing further is needed — the account is yours.</p>`,
|
|
3325
|
+
text: `Your password was removed
|
|
3326
|
+
|
|
3327
|
+
You just signed in with {{provider}}. A password had been set on this account,
|
|
3328
|
+
but this email address had never been confirmed \u2014 so we could not tell who set
|
|
3329
|
+
it, and it has been removed.
|
|
3330
|
+
|
|
3331
|
+
Signing in with {{provider}} works as before. If you want a password as well,
|
|
3332
|
+
set one from your account settings.
|
|
3333
|
+
|
|
3334
|
+
If you never set a password here, someone else may have tried to register with
|
|
3335
|
+
your address. Nothing further is needed \u2014 the account is yours.`
|
|
3274
3336
|
},
|
|
3275
3337
|
[MESSAGE_KEYS.oauthRegistration]: {
|
|
3276
3338
|
subject: "Welcome to {{appName}}",
|
|
@@ -3309,6 +3371,7 @@ If you did this, you're all set. If not, contact support right away.`
|
|
|
3309
3371
|
}
|
|
3310
3372
|
};
|
|
3311
3373
|
var SAMPLE_PAYLOADS = {
|
|
3374
|
+
[MESSAGE_KEYS.passwordRevoked]: { provider: "Google" },
|
|
3312
3375
|
[MESSAGE_KEYS.oauthRegistration]: { provider: "Google", appName: "Fonderie" },
|
|
3313
3376
|
[MESSAGE_KEYS.oauthLinked]: { provider: "Google" },
|
|
3314
3377
|
[MESSAGE_KEYS.oauthUnlinked]: { provider: "Google" },
|