@endora-commerce/mod-mfa 0.100.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +12 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AdminSecuritySettings.d.ts +3 -0
- package/dist/admin/pages/AdminSecuritySettings.d.ts.map +1 -0
- package/dist/admin/pages/AdminSecuritySettings.js +71 -0
- package/dist/admin/pages/AdminSecuritySettings.js.map +1 -0
- package/dist/backend/entities/mfa-enrolment.entity.d.ts +33 -0
- package/dist/backend/entities/mfa-enrolment.entity.d.ts.map +1 -0
- package/dist/backend/entities/mfa-enrolment.entity.js +95 -0
- package/dist/backend/entities/mfa-enrolment.entity.js.map +1 -0
- package/dist/backend/entities/mfa-organization-policy.entity.d.ts +19 -0
- package/dist/backend/entities/mfa-organization-policy.entity.d.ts.map +1 -0
- package/dist/backend/entities/mfa-organization-policy.entity.js +60 -0
- package/dist/backend/entities/mfa-organization-policy.entity.js.map +1 -0
- package/dist/backend/entities/mfa-recovery-code.entity.d.ts +17 -0
- package/dist/backend/entities/mfa-recovery-code.entity.d.ts.map +1 -0
- package/dist/backend/entities/mfa-recovery-code.entity.js +54 -0
- package/dist/backend/entities/mfa-recovery-code.entity.js.map +1 -0
- package/dist/backend/entities/mfa-social-identity.entity.d.ts +24 -0
- package/dist/backend/entities/mfa-social-identity.entity.d.ts.map +1 -0
- package/dist/backend/entities/mfa-social-identity.entity.js +73 -0
- package/dist/backend/entities/mfa-social-identity.entity.js.map +1 -0
- package/dist/backend/index.d.ts +131 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +173 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +99 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +111 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +23 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +93 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.oauth.d.ts +26 -0
- package/dist/backend/routes.oauth.d.ts.map +1 -0
- package/dist/backend/routes.oauth.js +155 -0
- package/dist/backend/routes.oauth.js.map +1 -0
- package/dist/backend/routes.org.d.ts +21 -0
- package/dist/backend/routes.org.d.ts.map +1 -0
- package/dist/backend/routes.org.js +17 -0
- package/dist/backend/routes.org.js.map +1 -0
- package/dist/backend/routes.public.d.ts +21 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +118 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/routes.self-service.d.ts +34 -0
- package/dist/backend/routes.self-service.d.ts.map +1 -0
- package/dist/backend/routes.self-service.js +113 -0
- package/dist/backend/routes.self-service.js.map +1 -0
- package/dist/backend/services/account-identity.d.ts +71 -0
- package/dist/backend/services/account-identity.d.ts.map +1 -0
- package/dist/backend/services/account-identity.js +87 -0
- package/dist/backend/services/account-identity.js.map +1 -0
- package/dist/backend/services/challenge-store.d.ts +40 -0
- package/dist/backend/services/challenge-store.d.ts.map +1 -0
- package/dist/backend/services/challenge-store.js +90 -0
- package/dist/backend/services/challenge-store.js.map +1 -0
- package/dist/backend/services/mfa-enrolment-count.service.d.ts +26 -0
- package/dist/backend/services/mfa-enrolment-count.service.d.ts.map +1 -0
- package/dist/backend/services/mfa-enrolment-count.service.js +34 -0
- package/dist/backend/services/mfa-enrolment-count.service.js.map +1 -0
- package/dist/backend/services/mfa-enrolment-service.d.ts +54 -0
- package/dist/backend/services/mfa-enrolment-service.d.ts.map +1 -0
- package/dist/backend/services/mfa-enrolment-service.js +217 -0
- package/dist/backend/services/mfa-enrolment-service.js.map +1 -0
- package/dist/backend/services/mfa-enrolment-state.service.d.ts +27 -0
- package/dist/backend/services/mfa-enrolment-state.service.d.ts.map +1 -0
- package/dist/backend/services/mfa-enrolment-state.service.js +33 -0
- package/dist/backend/services/mfa-enrolment-state.service.js.map +1 -0
- package/dist/backend/services/mfa-login-service.d.ts +56 -0
- package/dist/backend/services/mfa-login-service.d.ts.map +1 -0
- package/dist/backend/services/mfa-login-service.js +89 -0
- package/dist/backend/services/mfa-login-service.js.map +1 -0
- package/dist/backend/services/mfa-org-policy-service.d.ts +16 -0
- package/dist/backend/services/mfa-org-policy-service.d.ts.map +1 -0
- package/dist/backend/services/mfa-org-policy-service.js +43 -0
- package/dist/backend/services/mfa-org-policy-service.js.map +1 -0
- package/dist/backend/services/mfa-policy-resolver.d.ts +47 -0
- package/dist/backend/services/mfa-policy-resolver.d.ts.map +1 -0
- package/dist/backend/services/mfa-policy-resolver.js +82 -0
- package/dist/backend/services/mfa-policy-resolver.js.map +1 -0
- package/dist/backend/services/oauth-provider-service.d.ts +65 -0
- package/dist/backend/services/oauth-provider-service.d.ts.map +1 -0
- package/dist/backend/services/oauth-provider-service.js +80 -0
- package/dist/backend/services/oauth-provider-service.js.map +1 -0
- package/dist/backend/services/recovery-codes.d.ts +10 -0
- package/dist/backend/services/recovery-codes.d.ts.map +1 -0
- package/dist/backend/services/recovery-codes.js +18 -0
- package/dist/backend/services/recovery-codes.js.map +1 -0
- package/dist/backend/services/secret-cipher.d.ts +16 -0
- package/dist/backend/services/secret-cipher.d.ts.map +1 -0
- package/dist/backend/services/secret-cipher.js +52 -0
- package/dist/backend/services/secret-cipher.js.map +1 -0
- package/dist/backend/services/social-identity-service.d.ts +40 -0
- package/dist/backend/services/social-identity-service.d.ts.map +1 -0
- package/dist/backend/services/social-identity-service.js +69 -0
- package/dist/backend/services/social-identity-service.js.map +1 -0
- package/dist/backend/services/social-link-service.d.ts +63 -0
- package/dist/backend/services/social-link-service.d.ts.map +1 -0
- package/dist/backend/services/social-link-service.js +121 -0
- package/dist/backend/services/social-link-service.js.map +1 -0
- package/dist/backend/services/totp.d.ts +11 -0
- package/dist/backend/services/totp.d.ts.map +1 -0
- package/dist/backend/services/totp.js +46 -0
- package/dist/backend/services/totp.js.map +1 -0
- package/dist/manifest.d.ts +184 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +264 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140409_mfa_init.d.ts +22 -0
- package/dist/migrations/20260611T140409_mfa_init.d.ts.map +1 -0
- package/dist/migrations/20260611T140409_mfa_init.js +94 -0
- package/dist/migrations/20260611T140409_mfa_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/i18n/en.json +14 -0
- package/i18n/pl.json +14 -0
- package/package.json +102 -0
- package/tailwind.css +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# MFA module (feature 042)
|
|
2
|
+
|
|
3
|
+
Two-factor authentication (TOTP + recovery codes) and federated sign-in
|
|
4
|
+
(Google / Microsoft) for both the Storefront (customer accounts) and the
|
|
5
|
+
Admin UI (admin users).
|
|
6
|
+
|
|
7
|
+
See `specs/042-mfa-authentication/` for the full spec, plan, data model, and
|
|
8
|
+
contracts.
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
Complete (US1–US6). Capabilities:
|
|
13
|
+
|
|
14
|
+
- **TOTP 2FA** (enrol / activate / disable / regenerate / status) on both
|
|
15
|
+
surfaces, with single-use recovery codes and a replay-guarded second step.
|
|
16
|
+
- **Two-step login** — credential step returns `authenticated` |
|
|
17
|
+
`mfaRequired` | `mfaSetupRequired`; a separate verify step issues the session.
|
|
18
|
+
- **Enforcement** — per-scope settings (admin global; storefront global/channel)
|
|
19
|
+
and per-organization policy (org-admin + platform-admin); enforced-but-
|
|
20
|
+
unenrolled accounts enrol via a setup ticket at login.
|
|
21
|
+
- **Federated sign-in** — Google / Microsoft (OIDC via `openid-client`);
|
|
22
|
+
storefront auto-creates a standalone account, admin matches existing only.
|
|
23
|
+
- **Admin reset** — single account + organization-wide.
|
|
24
|
+
|
|
25
|
+
Self-service setup is gated on the per-scope "2FA enabled" setting (FR-001).
|
|
26
|
+
|
|
27
|
+
## Routes (by file)
|
|
28
|
+
|
|
29
|
+
- `routes.public.ts` — second-step verify + setup-ticket begin/complete (both surfaces)
|
|
30
|
+
- `routes.self-service.ts` — `/{account,admin/account}/mfa/*` (setup/activate/disable/regenerate/status)
|
|
31
|
+
- `routes.oauth.ts` — `/auth/{customer,admin}/oauth/:provider/{start,callback}`
|
|
32
|
+
- `routes.org.ts` — storefront org-admin enforcement
|
|
33
|
+
- `routes.admin.ts` — platform-admin reset (single/bulk) + org enforcement
|
|
34
|
+
|
|
35
|
+
## Notable decisions
|
|
36
|
+
|
|
37
|
+
- **TOTP**: reuses the existing `otpauth` dependency (no new dep). New
|
|
38
|
+
dependency `openid-client` is for Google/Microsoft OIDC (research §R2).
|
|
39
|
+
- **Secret at rest**: AES-256-GCM (`SecretCipher`), key from
|
|
40
|
+
`MFA_SECRET_ENCRYPTION_KEY` (base64, 32 bytes). Recovery codes are stored as
|
|
41
|
+
SHA-256 hashes only.
|
|
42
|
+
- **Ephemeral login state**: Redis (`ChallengeStore`) — pending-login
|
|
43
|
+
challenge, setup ticket, OAuth transaction — all TTL-bounded.
|
|
44
|
+
- **Module name `mfa`**: a singular acronym; a documented Principle VI
|
|
45
|
+
carve-out (see plan Complexity Tracking).
|
|
46
|
+
- **Removability** (Principle I): login services consult MFA through an
|
|
47
|
+
injected port; absent ⇒ password-only fallback. The legacy
|
|
48
|
+
`*.two_factor_secret` columns are superseded by `mfa_enrolments` and left
|
|
49
|
+
dormant (research §R8) — dropping them is tracked tech-debt.
|
|
50
|
+
|
|
51
|
+
## Environment
|
|
52
|
+
|
|
53
|
+
| Var | Purpose |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `MFA_SECRET_ENCRYPTION_KEY` | base64-encoded 32-byte AES key for TOTP secrets |
|
|
56
|
+
| `MFA_OAUTH_GOOGLE_CLIENT_ID` / `_SECRET` | Google OIDC client |
|
|
57
|
+
| `MFA_OAUTH_MICROSOFT_CLIENT_ID` / `_SECRET` | Microsoft Entra OIDC client |
|
|
58
|
+
| `MFA_OAUTH_*_REDIRECT_URI` | per-surface callback URLs |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mfa`' admin surface — one route and no sidebar entry, declared by the module
|
|
3
|
+
* that owns it (feature 091, Phase 4, batch four;
|
|
4
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
|
|
5
|
+
*
|
|
6
|
+
* **The nav-less shape, and it is the reason this module is in the batch.**
|
|
7
|
+
* Batch two rejected `mfa` on the ground that it contributes no sidebar entry,
|
|
8
|
+
* so *"half the ratchet would not move and half the evidence would be
|
|
9
|
+
* missing"*. `plan.md`'s Ruling 1 measured that as sound pilot-evidence
|
|
10
|
+
* reasoning and wrong as a selection rule: `admin/src/App.tsx`'s `ModuleRoute`
|
|
11
|
+
* gates **every** registry-contributed route on `useSurfaceVisibility` — both
|
|
12
|
+
* axes, rendering `NotFoundPage` — so a nav-less module's off-state proof is
|
|
13
|
+
* driven over the **route**, which is what an operator following a stale deep
|
|
14
|
+
* link actually meets. `admin/test/modules/mfa.module-owned-surface.test.tsx`
|
|
15
|
+
* is that proof, and the ratchet's `{ routes: 1, nav: 0 }` entry is removed
|
|
16
|
+
* like any other: a zero that stays zero is a passing assertion, not a missing
|
|
17
|
+
* one.
|
|
18
|
+
*
|
|
19
|
+
* **The route carries no `requiredPermission`, and that is read from the route
|
|
20
|
+
* rather than guessed.** `/security` is the signed-in admin's own two-factor
|
|
21
|
+
* self-service (`packages/modules/mfa/src/backend/routes.self-service.ts`), and
|
|
22
|
+
* every one of its six endpoints is gated by the bare admin guard —
|
|
23
|
+
* `{ preHandler: requireGuard }`, no `requireAdmin(...)` anywhere. An operator
|
|
24
|
+
* manages their own second factor; there is no permission code that could gate
|
|
25
|
+
* it without locking somebody out of their own account. So the declaration
|
|
26
|
+
* omits the field, `isSurfaceVisible` treats an absent requirement as
|
|
27
|
+
* satisfied, and the module's presence is the whole of the gate.
|
|
28
|
+
*
|
|
29
|
+
* **This entry exports data and nothing else** (R2), like every `./admin`
|
|
30
|
+
* layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
|
|
31
|
+
* surface when it emits no runtime binding, and `./admin` deliberately does not
|
|
32
|
+
* qualify, so a consumer reaching into another module's `./admin` stays a
|
|
33
|
+
* counted boundary reach.
|
|
34
|
+
*/
|
|
35
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
36
|
+
export declare const contributions: AdminContributions;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAKnF,eAAO,MAAM,aAAa,EAAE,kBAQ3B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The module's only admin route: the signed-in admin's own 2FA settings. */
|
|
2
|
+
const ROUTE_PATH = '/security';
|
|
3
|
+
export const contributions = {
|
|
4
|
+
routes: [
|
|
5
|
+
{
|
|
6
|
+
path: ROUTE_PATH,
|
|
7
|
+
component: () => import('./pages/AdminSecuritySettings.js'),
|
|
8
|
+
index: true,
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAoCA,6EAA6E;AAC7E,MAAM,UAAU,GAAG,WAAW,CAAC;AAE/B,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,kCAAkC,CAAC;YAC3D,KAAK,EAAE,IAAI;SACZ;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminSecuritySettings.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/AdminSecuritySettings.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA+BzF,MAAM,CAAC,OAAO,UAAU,qBAAqB,IAAI,SAAS,CAmKzD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { ShieldCheck, ShieldAlert } from 'lucide-react';
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, } from '@endora-commerce/admin-kit/ui';
|
|
5
|
+
import { ApiError, apiClient } from '@endora-commerce/admin-kit/lib';
|
|
6
|
+
const BASE = '/api/v1/admin/account/mfa';
|
|
7
|
+
export default function AdminSecuritySettings() {
|
|
8
|
+
const [status, setStatus] = useState(null);
|
|
9
|
+
const [setup, setSetup] = useState(null);
|
|
10
|
+
const [recoveryCodes, setRecoveryCodes] = useState(null);
|
|
11
|
+
const [code, setCode] = useState('');
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [busy, setBusy] = useState(false);
|
|
14
|
+
const loadStatus = useCallback(async () => {
|
|
15
|
+
const res = await apiClient.get(`${BASE}/status`);
|
|
16
|
+
setStatus(res.data);
|
|
17
|
+
}, []);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
void loadStatus();
|
|
20
|
+
}, [loadStatus]);
|
|
21
|
+
const run = useCallback(async (fn) => {
|
|
22
|
+
setBusy(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
await fn();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
setError(err instanceof ApiError ? err.envelope.error.message : 'Something went wrong.');
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
setBusy(false);
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
const startSetup = () => void run(async () => {
|
|
35
|
+
const res = await apiClient.post(`${BASE}/setup`);
|
|
36
|
+
setSetup(res.data);
|
|
37
|
+
setRecoveryCodes(null);
|
|
38
|
+
});
|
|
39
|
+
const confirm = (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
void run(async () => {
|
|
42
|
+
const res = await apiClient.post(`${BASE}/activate`, {
|
|
43
|
+
code,
|
|
44
|
+
});
|
|
45
|
+
setRecoveryCodes(res.data.recoveryCodes);
|
|
46
|
+
setSetup(null);
|
|
47
|
+
setCode('');
|
|
48
|
+
await loadStatus();
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const disable = (e) => {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
void run(async () => {
|
|
54
|
+
await apiClient.post(`${BASE}/disable`, { code });
|
|
55
|
+
setCode('');
|
|
56
|
+
setRecoveryCodes(null);
|
|
57
|
+
await loadStatus();
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const regenerate = (e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
void run(async () => {
|
|
63
|
+
const res = await apiClient.post(`${BASE}/recovery-codes/regenerate`, { code });
|
|
64
|
+
setRecoveryCodes(res.data.recoveryCodes);
|
|
65
|
+
setCode('');
|
|
66
|
+
await loadStatus();
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
return (_jsx("div", { className: "mx-auto max-w-2xl space-y-6 p-4", children: _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-lg", children: [_jsx(ShieldCheck, { className: "size-5 text-primary" }), "Two-factor authentication"] }), _jsx(CardDescription, { children: "Protect your admin account with a time-based code from an authenticator app." })] }), _jsxs(CardContent, { className: "space-y-4", children: [error ? (_jsxs(Alert, { variant: "destructive", children: [_jsx(ShieldAlert, { className: "size-4" }), _jsx(AlertTitle, { children: "Error" }), _jsx(AlertDescription, { children: error })] })) : null, recoveryCodes ? (_jsxs(Alert, { children: [_jsx(AlertTitle, { children: "Save your recovery codes" }), _jsxs(AlertDescription, { children: [_jsx("p", { children: "Each code works once. They are shown only now." }), _jsx("ul", { className: "mt-2 grid grid-cols-2 gap-1 font-mono text-sm", children: recoveryCodes.map((c) => (_jsx("li", { children: c }, c))) })] })] })) : null, status === null ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Loading\u2026" })) : status.totpActive ? (_jsxs("div", { className: "space-y-6", children: [_jsxs("p", { className: "text-sm", children: ["2FA is ", _jsx("strong", { children: "enabled" }), ". Unused recovery codes: ", status.recoveryCodesRemaining, "."] }), _jsxs("form", { className: "space-y-2", onSubmit: disable, children: [_jsx(Label, { htmlFor: "disable-code", children: "Enter a current code to disable 2FA" }), _jsx(Input, { id: "disable-code", value: code, onChange: (e) => setCode(e.target.value), required: true }), _jsx(Button, { type: "submit", variant: "destructive", disabled: busy, children: "Disable 2FA" })] }), _jsxs("form", { className: "space-y-2", onSubmit: regenerate, children: [_jsx(Label, { htmlFor: "regen-code", children: "Enter a current code to regenerate recovery codes" }), _jsx(Input, { id: "regen-code", value: code, onChange: (e) => setCode(e.target.value), required: true }), _jsx(Button, { type: "submit", variant: "outline", disabled: busy, children: "Regenerate recovery codes" })] })] })) : setup ? (_jsxs("form", { className: "space-y-3", onSubmit: confirm, children: [_jsx("p", { className: "text-sm", children: "Add this account to your authenticator app:" }), _jsx("p", { className: "break-all rounded bg-muted px-2 py-1 font-mono text-xs", children: setup.otpauthUri }), _jsxs("p", { className: "text-sm", children: ["Manual key: ", _jsx("code", { className: "font-mono", children: setup.secret })] }), _jsx(Label, { htmlFor: "confirm-code", children: "Enter the 6-digit code to confirm" }), _jsx(Input, { id: "confirm-code", inputMode: "numeric", value: code, onChange: (e) => setCode(e.target.value), required: true, placeholder: "123456" }), _jsx(Button, { type: "submit", disabled: busy, children: "Confirm and enable" })] })) : (_jsx(Button, { onClick: startSetup, disabled: busy, children: "Enable 2FA" }))] })] }) }));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=AdminSecuritySettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminSecuritySettings.js","sourceRoot":"","sources":["../../../src/admin/pages/AdminSecuritySettings.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Per-account TOTP enrolment (feature 042). At most one `active` row per
|
|
4
|
+
* subject (enforced by a partial unique index, see migration 067).
|
|
5
|
+
*
|
|
6
|
+
* The subject is polymorphic across the two identity stores
|
|
7
|
+
* (`customer_accounts` / `admin_users`); no DB FK is declared to those tables
|
|
8
|
+
* to keep the `mfa` module isolated (Constitution Principle I). The TOTP
|
|
9
|
+
* secret is stored encrypted at rest (AES-256-GCM); the plaintext base32
|
|
10
|
+
* secret never touches the database.
|
|
11
|
+
*/
|
|
12
|
+
export declare class MfaEnrolment {
|
|
13
|
+
[OptionalProps]?: 'id' | 'status' | 'lastAcceptedStep' | 'confirmedAt' | 'createdAt' | 'updatedAt';
|
|
14
|
+
id: string;
|
|
15
|
+
/** `'customer'` | `'admin'`. */
|
|
16
|
+
subjectType: string;
|
|
17
|
+
/** `customer_accounts.id` or `admin_users.id` — service-enforced, no DB FK. */
|
|
18
|
+
subjectId: string;
|
|
19
|
+
/** `'pending'` (setup started, unconfirmed) | `'active'`. */
|
|
20
|
+
status: string;
|
|
21
|
+
/** AES-256-GCM ciphertext of the base32 TOTP secret. */
|
|
22
|
+
secretCiphertext: Buffer;
|
|
23
|
+
/** GCM initialisation vector (per row). */
|
|
24
|
+
secretIv: Buffer;
|
|
25
|
+
/** GCM authentication tag. */
|
|
26
|
+
secretAuthTag: Buffer;
|
|
27
|
+
/** Last accepted TOTP time-step — guards against in-window replay. */
|
|
28
|
+
lastAcceptedStep?: string | null;
|
|
29
|
+
confirmedAt?: Date | null;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
updatedAt: Date;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=mfa-enrolment.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-enrolment.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/mfa-enrolment.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;;;;;;GASG;AACH,qBAEa,YAAY;IACvB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,QAAQ,GACR,kBAAkB,GAClB,aAAa,GACb,WAAW,GACX,WAAW,CAAC;IAGhB,EAAE,EAAE,MAAM,CAAgB;IAE1B,gCAAgC;IAGhC,WAAW,EAAG,MAAM,CAAC;IAErB,+EAA+E;IAG/E,SAAS,EAAG,MAAM,CAAC;IAEnB,6DAA6D;IAE7D,MAAM,EAAE,MAAM,CAAa;IAE3B,wDAAwD;IAExD,gBAAgB,EAAG,MAAM,CAAC;IAE1B,2CAA2C;IAE3C,QAAQ,EAAG,MAAM,CAAC;IAElB,8BAA8B;IAE9B,aAAa,EAAG,MAAM,CAAC;IAEvB,sEAAsE;IAEtE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGjC,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAG1B,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Per-account TOTP enrolment (feature 042). At most one `active` row per
|
|
15
|
+
* subject (enforced by a partial unique index, see migration 067).
|
|
16
|
+
*
|
|
17
|
+
* The subject is polymorphic across the two identity stores
|
|
18
|
+
* (`customer_accounts` / `admin_users`); no DB FK is declared to those tables
|
|
19
|
+
* to keep the `mfa` module isolated (Constitution Principle I). The TOTP
|
|
20
|
+
* secret is stored encrypted at rest (AES-256-GCM); the plaintext base32
|
|
21
|
+
* secret never touches the database.
|
|
22
|
+
*/
|
|
23
|
+
let MfaEnrolment = class MfaEnrolment {
|
|
24
|
+
[OptionalProps];
|
|
25
|
+
id = randomUUID();
|
|
26
|
+
/** `'customer'` | `'admin'`. */
|
|
27
|
+
subjectType;
|
|
28
|
+
/** `customer_accounts.id` or `admin_users.id` — service-enforced, no DB FK. */
|
|
29
|
+
subjectId;
|
|
30
|
+
/** `'pending'` (setup started, unconfirmed) | `'active'`. */
|
|
31
|
+
status = 'pending';
|
|
32
|
+
/** AES-256-GCM ciphertext of the base32 TOTP secret. */
|
|
33
|
+
secretCiphertext;
|
|
34
|
+
/** GCM initialisation vector (per row). */
|
|
35
|
+
secretIv;
|
|
36
|
+
/** GCM authentication tag. */
|
|
37
|
+
secretAuthTag;
|
|
38
|
+
/** Last accepted TOTP time-step — guards against in-window replay. */
|
|
39
|
+
lastAcceptedStep;
|
|
40
|
+
confirmedAt;
|
|
41
|
+
createdAt = new Date();
|
|
42
|
+
updatedAt = new Date();
|
|
43
|
+
};
|
|
44
|
+
__decorate([
|
|
45
|
+
PrimaryKey({ type: 'uuid' }),
|
|
46
|
+
__metadata("design:type", String)
|
|
47
|
+
], MfaEnrolment.prototype, "id", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
Property({ type: 'string', length: 16 }),
|
|
50
|
+
Index(),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], MfaEnrolment.prototype, "subjectType", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
Property({ type: 'uuid' }),
|
|
55
|
+
Index(),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], MfaEnrolment.prototype, "subjectId", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Property({ type: 'string', length: 16 }),
|
|
60
|
+
__metadata("design:type", String)
|
|
61
|
+
], MfaEnrolment.prototype, "status", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
Property({ type: 'bytea' }),
|
|
64
|
+
__metadata("design:type", Buffer)
|
|
65
|
+
], MfaEnrolment.prototype, "secretCiphertext", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
Property({ type: 'bytea' }),
|
|
68
|
+
__metadata("design:type", Buffer)
|
|
69
|
+
], MfaEnrolment.prototype, "secretIv", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
Property({ type: 'bytea' }),
|
|
72
|
+
__metadata("design:type", Buffer)
|
|
73
|
+
], MfaEnrolment.prototype, "secretAuthTag", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
Property({ type: 'bigint', nullable: true }),
|
|
76
|
+
__metadata("design:type", Object)
|
|
77
|
+
], MfaEnrolment.prototype, "lastAcceptedStep", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
Property({ type: 'datetime', nullable: true }),
|
|
80
|
+
__metadata("design:type", Object)
|
|
81
|
+
], MfaEnrolment.prototype, "confirmedAt", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
84
|
+
__metadata("design:type", Date)
|
|
85
|
+
], MfaEnrolment.prototype, "createdAt", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
88
|
+
__metadata("design:type", Date)
|
|
89
|
+
], MfaEnrolment.prototype, "updatedAt", void 0);
|
|
90
|
+
MfaEnrolment = __decorate([
|
|
91
|
+
GlobalEntity(),
|
|
92
|
+
Entity({ tableName: 'mfa_enrolments' })
|
|
93
|
+
], MfaEnrolment);
|
|
94
|
+
export { MfaEnrolment };
|
|
95
|
+
//# sourceMappingURL=mfa-enrolment.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-enrolment.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/mfa-enrolment.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;GASG;AAGI,IAAM,YAAY,GAAlB,MAAM,YAAY;IACvB,CAAC,aAAa,CAAC,CAMC;IAGhB,EAAE,GAAW,UAAU,EAAE,CAAC;IAE1B,gCAAgC;IAGhC,WAAW,CAAU;IAErB,+EAA+E;IAG/E,SAAS,CAAU;IAEnB,6DAA6D;IAE7D,MAAM,GAAW,SAAS,CAAC;IAE3B,wDAAwD;IAExD,gBAAgB,CAAU;IAE1B,2CAA2C;IAE3C,QAAQ,CAAU;IAElB,8BAA8B;IAE9B,aAAa,CAAU;IAEvB,sEAAsE;IAEtE,gBAAgB,CAAiB;IAGjC,WAAW,CAAe;IAG1B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAxCC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wCACH;AAK1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;iDACa;AAKrB;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;+CACW;AAInB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;4CACd;AAI3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;8BACT,MAAM;sDAAC;AAI1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;8BACjB,MAAM;8CAAC;AAIlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;8BACZ,MAAM;mDAAC;AAIvB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;sDACZ;AAGjC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACrB;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;+CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;+CAAc;AAjDlB,YAAY;IAFxB,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;GAC3B,YAAY,CAkDxB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Per-Organization 2FA enforcement flag (feature 042, FR-014). Managed by
|
|
4
|
+
* Organization Administrators (storefront) and Platform Administrators
|
|
5
|
+
* (admin). Owned by the `mfa` module for removability; `organizationId`
|
|
6
|
+
* references the organizations module by value (no hard FK) to preserve
|
|
7
|
+
* module isolation (Constitution Principle I). Absence of a row ⇒ not enforced.
|
|
8
|
+
*/
|
|
9
|
+
export declare class MfaOrganizationPolicy {
|
|
10
|
+
[OptionalProps]?: 'id' | 'enforceTotp' | 'updatedByActor' | 'createdAt' | 'updatedAt';
|
|
11
|
+
id: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
enforceTotp: boolean;
|
|
14
|
+
/** Audit hint — admin user id or org-admin customer id that last changed it. */
|
|
15
|
+
updatedByActor?: string | null;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
updatedAt: Date;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=mfa-organization-policy.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-organization-policy.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/mfa-organization-policy.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAItF;;;;;;GAMG;AACH,qBAEa,qBAAqB;IAChC,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,GAAG,aAAa,GAAG,gBAAgB,GAAG,WAAW,GAAG,WAAW,CAAC;IAGtF,EAAE,EAAE,MAAM,CAAgB;IAI1B,cAAc,EAAG,MAAM,CAAC;IAGxB,WAAW,EAAE,OAAO,CAAS;IAE7B,gFAAgF;IAEhF,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/B,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Per-Organization 2FA enforcement flag (feature 042, FR-014). Managed by
|
|
15
|
+
* Organization Administrators (storefront) and Platform Administrators
|
|
16
|
+
* (admin). Owned by the `mfa` module for removability; `organizationId`
|
|
17
|
+
* references the organizations module by value (no hard FK) to preserve
|
|
18
|
+
* module isolation (Constitution Principle I). Absence of a row ⇒ not enforced.
|
|
19
|
+
*/
|
|
20
|
+
let MfaOrganizationPolicy = class MfaOrganizationPolicy {
|
|
21
|
+
[OptionalProps];
|
|
22
|
+
id = randomUUID();
|
|
23
|
+
organizationId;
|
|
24
|
+
enforceTotp = false;
|
|
25
|
+
/** Audit hint — admin user id or org-admin customer id that last changed it. */
|
|
26
|
+
updatedByActor;
|
|
27
|
+
createdAt = new Date();
|
|
28
|
+
updatedAt = new Date();
|
|
29
|
+
};
|
|
30
|
+
__decorate([
|
|
31
|
+
PrimaryKey({ type: 'uuid' }),
|
|
32
|
+
__metadata("design:type", String)
|
|
33
|
+
], MfaOrganizationPolicy.prototype, "id", void 0);
|
|
34
|
+
__decorate([
|
|
35
|
+
Property({ type: 'uuid' }),
|
|
36
|
+
Unique(),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], MfaOrganizationPolicy.prototype, "organizationId", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
Property({ type: 'boolean' }),
|
|
41
|
+
__metadata("design:type", Boolean)
|
|
42
|
+
], MfaOrganizationPolicy.prototype, "enforceTotp", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
Property({ type: 'string', length: 64, nullable: true }),
|
|
45
|
+
__metadata("design:type", Object)
|
|
46
|
+
], MfaOrganizationPolicy.prototype, "updatedByActor", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
49
|
+
__metadata("design:type", Date)
|
|
50
|
+
], MfaOrganizationPolicy.prototype, "createdAt", void 0);
|
|
51
|
+
__decorate([
|
|
52
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
53
|
+
__metadata("design:type", Date)
|
|
54
|
+
], MfaOrganizationPolicy.prototype, "updatedAt", void 0);
|
|
55
|
+
MfaOrganizationPolicy = __decorate([
|
|
56
|
+
GlobalEntity(),
|
|
57
|
+
Entity({ tableName: 'mfa_organization_policies' })
|
|
58
|
+
], MfaOrganizationPolicy);
|
|
59
|
+
export { MfaOrganizationPolicy };
|
|
60
|
+
//# sourceMappingURL=mfa-organization-policy.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-organization-policy.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/mfa-organization-policy.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;GAMG;AAGI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,CAAC,aAAa,CAAC,CAAuE;IAGtF,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,cAAc,CAAU;IAGxB,WAAW,GAAY,KAAK,CAAC;IAE7B,gFAAgF;IAEhF,cAAc,CAAiB;IAG/B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAlBC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;iDACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,EAAE;;6DACe;AAGxB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;0DACD;AAI7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6DAC1B;AAG/B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AArBlB,qBAAqB;IAFjC,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;GACtC,qBAAqB,CAsBjC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Single-use backup code for a TOTP enrolment (feature 042). Stored only as a
|
|
4
|
+
* SHA-256 hash; the plaintext is shown once at activation/regeneration.
|
|
5
|
+
* Cascade-deleted with its parent enrolment (FK declared in migration 067).
|
|
6
|
+
*/
|
|
7
|
+
export declare class MfaRecoveryCode {
|
|
8
|
+
[OptionalProps]?: 'id' | 'usedAt' | 'createdAt';
|
|
9
|
+
id: string;
|
|
10
|
+
enrolmentId: string;
|
|
11
|
+
/** SHA-256 hex of the plaintext recovery code. */
|
|
12
|
+
codeHash: string;
|
|
13
|
+
/** Set when the code is consumed; a used code is never re-accepted. */
|
|
14
|
+
usedAt?: Date | null;
|
|
15
|
+
createdAt: Date;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=mfa-recovery-code.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-recovery-code.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/mfa-recovery-code.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;GAIG;AACH,qBAEa,eAAe;IAC1B,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC;IAGhD,EAAE,EAAE,MAAM,CAAgB;IAI1B,WAAW,EAAG,MAAM,CAAC;IAErB,kDAAkD;IAElD,QAAQ,EAAG,MAAM,CAAC;IAElB,uEAAuE;IAEvE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAGrB,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Single-use backup code for a TOTP enrolment (feature 042). Stored only as a
|
|
15
|
+
* SHA-256 hash; the plaintext is shown once at activation/regeneration.
|
|
16
|
+
* Cascade-deleted with its parent enrolment (FK declared in migration 067).
|
|
17
|
+
*/
|
|
18
|
+
let MfaRecoveryCode = class MfaRecoveryCode {
|
|
19
|
+
[OptionalProps];
|
|
20
|
+
id = randomUUID();
|
|
21
|
+
enrolmentId;
|
|
22
|
+
/** SHA-256 hex of the plaintext recovery code. */
|
|
23
|
+
codeHash;
|
|
24
|
+
/** Set when the code is consumed; a used code is never re-accepted. */
|
|
25
|
+
usedAt;
|
|
26
|
+
createdAt = new Date();
|
|
27
|
+
};
|
|
28
|
+
__decorate([
|
|
29
|
+
PrimaryKey({ type: 'uuid' }),
|
|
30
|
+
__metadata("design:type", String)
|
|
31
|
+
], MfaRecoveryCode.prototype, "id", void 0);
|
|
32
|
+
__decorate([
|
|
33
|
+
Property({ type: 'uuid' }),
|
|
34
|
+
Index(),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], MfaRecoveryCode.prototype, "enrolmentId", void 0);
|
|
37
|
+
__decorate([
|
|
38
|
+
Property({ type: 'string', length: 128 }),
|
|
39
|
+
__metadata("design:type", String)
|
|
40
|
+
], MfaRecoveryCode.prototype, "codeHash", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
Property({ type: 'datetime', nullable: true }),
|
|
43
|
+
__metadata("design:type", Object)
|
|
44
|
+
], MfaRecoveryCode.prototype, "usedAt", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
47
|
+
__metadata("design:type", Date)
|
|
48
|
+
], MfaRecoveryCode.prototype, "createdAt", void 0);
|
|
49
|
+
MfaRecoveryCode = __decorate([
|
|
50
|
+
GlobalEntity(),
|
|
51
|
+
Entity({ tableName: 'mfa_recovery_codes' })
|
|
52
|
+
], MfaRecoveryCode);
|
|
53
|
+
export { MfaRecoveryCode };
|
|
54
|
+
//# sourceMappingURL=mfa-recovery-code.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-recovery-code.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/mfa-recovery-code.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;GAIG;AAGI,IAAM,eAAe,GAArB,MAAM,eAAe;IAC1B,CAAC,aAAa,CAAC,CAAiC;IAGhD,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,WAAW,CAAU;IAErB,kDAAkD;IAElD,QAAQ,CAAU;IAElB,uEAAuE;IAEvE,MAAM,CAAe;IAGrB,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAhBC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2CACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;oDACa;AAIrB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;iDACxB;AAIlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAC1B;AAGrB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;kDAAc;AAnBlB,eAAe;IAF3B,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;GAC/B,eAAe,CAoB3B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Link between a platform account and an external provider identity
|
|
4
|
+
* (Google / Microsoft) — feature 042. Resolution is by the provider's
|
|
5
|
+
* verified primary email; the `providerSubject` (`sub` claim) is the stable
|
|
6
|
+
* provider-side id. Uniqueness constraints (declared in migration 067):
|
|
7
|
+
* - (provider, provider_subject): one platform account per IdP identity;
|
|
8
|
+
* - (subject_type, subject_id, provider): one link per provider per account.
|
|
9
|
+
*/
|
|
10
|
+
export declare class MfaSocialIdentity {
|
|
11
|
+
[OptionalProps]?: 'id' | 'lastUsedAt' | 'linkedAt';
|
|
12
|
+
id: string;
|
|
13
|
+
subjectType: string;
|
|
14
|
+
subjectId: string;
|
|
15
|
+
/** `'google'` | `'microsoft'`. */
|
|
16
|
+
provider: string;
|
|
17
|
+
/** The IdP `sub` claim — stable provider-side identifier. */
|
|
18
|
+
providerSubject: string;
|
|
19
|
+
/** Verified email at link time (used for resolution). */
|
|
20
|
+
email: string;
|
|
21
|
+
linkedAt: Date;
|
|
22
|
+
lastUsedAt?: Date | null;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=mfa-social-identity.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-social-identity.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/mfa-social-identity.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;;;;GAOG;AACH,qBAEa,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,UAAU,CAAC;IAGnD,EAAE,EAAE,MAAM,CAAgB;IAG1B,WAAW,EAAG,MAAM,CAAC;IAGrB,SAAS,EAAG,MAAM,CAAC;IAEnB,kCAAkC;IAElC,QAAQ,EAAG,MAAM,CAAC;IAElB,6DAA6D;IAE7D,eAAe,EAAG,MAAM,CAAC;IAEzB,yDAAyD;IAGzD,KAAK,EAAG,MAAM,CAAC;IAGf,QAAQ,EAAE,IAAI,CAAc;IAG5B,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B"}
|