@voltro/plugin-auth 0.84.0 → 0.86.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 +1215 -0
- package/README.md +10 -0
- package/THIRD-PARTY-NOTICES.md +5 -89
- package/dist/admission.js +9 -0
- package/dist/appUserStore.js +19 -0
- package/dist/emailVerification.js +62 -0
- package/dist/handlers.js +337 -0
- package/dist/impersonation.js +175 -0
- package/dist/index.d.ts +19 -7
- package/dist/index.js +22 -88
- package/dist/invitations.js +187 -0
- package/dist/mail.js +10 -0
- package/dist/memberships.js +8 -0
- package/dist/nativeRoutes.js +120 -0
- package/dist/passkeyHandlers.js +162 -0
- package/dist/password.js +2 -2
- package/dist/plugin.d.ts +5 -1
- package/dist/plugin.js +498 -5
- package/dist/postgresUserStore.js +463 -0
- package/dist/revocation.js +33 -0
- package/dist/schema.d.ts +4 -5
- package/dist/schema.js +3 -1
- package/dist/sessionRegistry.js +118 -0
- package/dist/strategy.d.ts +4 -0
- package/dist/strategy.js +65 -3
- package/dist/subjectGuard.js +11 -0
- package/dist/totp.js +55 -0
- package/dist/userStore.js +285 -0
- package/dist/web/passkeyClient.js +78 -0
- package/dist/web/requireAuth.js +9 -0
- package/dist/web/subjectContext.js +13 -0
- package/dist/web.js +4 -90
- package/package.json +4 -5
- package/dist/plugin-CNW-X70p.js +0 -2015
- package/dist/strategy-XaGjSicX.js +0 -353
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { newAuthId as e, subjectFromUser as t } from "./userStore.js";
|
|
2
|
+
import { Effect as n } from "effect";
|
|
3
|
+
import { IMPERSONATION_METADATA_KEY as r, rawImpersonationMark as i } from "@voltro/protocol";
|
|
4
|
+
//#region src/impersonation.ts
|
|
5
|
+
var a = 900, o = [
|
|
6
|
+
"/mfa/enroll/start",
|
|
7
|
+
"/mfa/enroll/verify",
|
|
8
|
+
"/mfa/unenroll",
|
|
9
|
+
"/mfa/recovery-codes/regenerate",
|
|
10
|
+
"/passkey/register/options",
|
|
11
|
+
"/passkey/register/verify",
|
|
12
|
+
"/sessions/revoke-others",
|
|
13
|
+
"/switch-tenant",
|
|
14
|
+
"/impersonate/start"
|
|
15
|
+
], s = (e) => {
|
|
16
|
+
if (typeof e != "object" || !e) return null;
|
|
17
|
+
let t = e;
|
|
18
|
+
return typeof t.actorUserId != "string" || t.actorUserId.length === 0 || typeof t.grantId != "string" || typeof t.expiresAt != "string" || typeof t.actorSessionId != "string" || typeof t.startedAt != "string" ? null : {
|
|
19
|
+
actorUserId: t.actorUserId,
|
|
20
|
+
actorSessionId: t.actorSessionId,
|
|
21
|
+
grantId: t.grantId,
|
|
22
|
+
startedAt: t.startedAt,
|
|
23
|
+
expiresAt: t.expiresAt,
|
|
24
|
+
...typeof t.reason == "string" ? { reason: t.reason } : {}
|
|
25
|
+
};
|
|
26
|
+
}, c = (e) => e.type === "user" ? s(i(e)) : null, l = (e) => c(e) !== null, u = () => (e) => {
|
|
27
|
+
if (e.type !== "user" || e.metadata === void 0) return e;
|
|
28
|
+
let t = s(e.metadata[r]), { metadata: n, ...i } = e;
|
|
29
|
+
return {
|
|
30
|
+
...i,
|
|
31
|
+
metadata: t === null ? { __redacted: "all" } : {
|
|
32
|
+
__redacted: "all-except-impersonation",
|
|
33
|
+
[r]: t
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, d = (e, t, n = {}) => ({
|
|
37
|
+
status: e,
|
|
38
|
+
body: JSON.stringify(t),
|
|
39
|
+
contentType: "application/json; charset=utf-8",
|
|
40
|
+
...n
|
|
41
|
+
}), f = (e, t) => n.suspend(() => {
|
|
42
|
+
let r = e.audit(t);
|
|
43
|
+
return r === void 0 ? n.void : n.isEffect(r) ? r : n.tryPromise({
|
|
44
|
+
try: () => Promise.resolve(r),
|
|
45
|
+
catch: (e) => e
|
|
46
|
+
});
|
|
47
|
+
}).pipe(n.catchCause(() => n.void)), p = (i, a, o) => n.gen(function* () {
|
|
48
|
+
let s = o.impersonation, c = (o.now ?? Date.now)(), u = new Date(c), p = i.actorSubject, m = (e, t, r = {}) => f(s, {
|
|
49
|
+
kind: "refused",
|
|
50
|
+
at: u,
|
|
51
|
+
actorUserId: r.actorUser?.id ?? (p.type === "user" ? p.id : null),
|
|
52
|
+
actorEmail: r.actorUser?.email ?? null,
|
|
53
|
+
targetUserId: r.target?.id ?? null,
|
|
54
|
+
targetEmail: r.target?.email ?? null,
|
|
55
|
+
tenantId: p.tenantId ?? null,
|
|
56
|
+
reason: i.reason ?? null,
|
|
57
|
+
refusal: t,
|
|
58
|
+
ipAddress: i.ipAddress ?? null,
|
|
59
|
+
userAgent: i.userAgent ?? null
|
|
60
|
+
}).pipe(n.as(d(e, {
|
|
61
|
+
error: t,
|
|
62
|
+
...r.message === void 0 ? {} : { message: r.message }
|
|
63
|
+
})));
|
|
64
|
+
if (p.type !== "user") return yield* m(401, "unauthenticated");
|
|
65
|
+
if (l(p)) return yield* m(403, "nested_impersonation", { message: "An impersonated session may not start another impersonation. Stop the current one first." });
|
|
66
|
+
if (!i.actorSessionId) return yield* m(400, "no_actor_session");
|
|
67
|
+
let h = yield* a.findById(p.id);
|
|
68
|
+
if (!h) return yield* m(401, "unauthenticated");
|
|
69
|
+
if (!i.targetUserId && !i.targetEmail) return yield* m(400, "target_required", { actorUser: h });
|
|
70
|
+
let g = i.targetUserId ? yield* a.findById(i.targetUserId) : yield* a.findByEmail(i.targetEmail);
|
|
71
|
+
if (!g) return yield* m(404, "target_not_found", { actorUser: h });
|
|
72
|
+
if (g.id === h.id) return yield* m(400, "self_impersonation", {
|
|
73
|
+
actorUser: h,
|
|
74
|
+
target: g
|
|
75
|
+
});
|
|
76
|
+
let _ = i.reason?.trim() ?? "";
|
|
77
|
+
if ((s.requireReason ?? !0) && _.length === 0) return yield* m(400, "reason_required", {
|
|
78
|
+
actorUser: h,
|
|
79
|
+
target: g,
|
|
80
|
+
message: "A reason is required to impersonate a user."
|
|
81
|
+
});
|
|
82
|
+
if (!(yield* s.authority({
|
|
83
|
+
actor: p,
|
|
84
|
+
actorUser: h,
|
|
85
|
+
target: g
|
|
86
|
+
}).pipe(n.catchCause(() => n.succeed(!1))))) return yield* m(403, "not_permitted", {
|
|
87
|
+
actorUser: h,
|
|
88
|
+
target: g
|
|
89
|
+
});
|
|
90
|
+
if (yield* s.authority({
|
|
91
|
+
actor: t(g),
|
|
92
|
+
actorUser: g,
|
|
93
|
+
target: h
|
|
94
|
+
}).pipe(n.catchCause(() => n.succeed(!0)))) return yield* m(403, "target_may_impersonate", {
|
|
95
|
+
actorUser: h,
|
|
96
|
+
target: g,
|
|
97
|
+
message: "This user may themselves impersonate others and cannot be impersonated."
|
|
98
|
+
});
|
|
99
|
+
let v = s.maxDurationSeconds ?? 900, y = Math.max(1, Math.min(i.durationSeconds ?? v, v)), b = new Date(c + y * 1e3), x = e("sess"), S = yield* a.insertImpersonationGrant({
|
|
100
|
+
id: e("imp"),
|
|
101
|
+
sessionId: x,
|
|
102
|
+
actorUserId: h.id,
|
|
103
|
+
actorSessionId: i.actorSessionId,
|
|
104
|
+
targetUserId: g.id,
|
|
105
|
+
tenantId: g.tenantId,
|
|
106
|
+
reason: _.length > 0 ? _ : null,
|
|
107
|
+
expiresAt: b,
|
|
108
|
+
ipAddress: i.ipAddress ?? null,
|
|
109
|
+
userAgent: i.userAgent ?? null
|
|
110
|
+
}), C = {
|
|
111
|
+
actorUserId: h.id,
|
|
112
|
+
actorSessionId: i.actorSessionId,
|
|
113
|
+
grantId: S.id,
|
|
114
|
+
startedAt: S.startedAt.toISOString(),
|
|
115
|
+
expiresAt: b.toISOString(),
|
|
116
|
+
..._.length > 0 ? { reason: _ } : {}
|
|
117
|
+
}, w = yield* o.issuer.issue(g, {
|
|
118
|
+
sessionId: x,
|
|
119
|
+
ttlSeconds: y,
|
|
120
|
+
metadata: { [r]: C },
|
|
121
|
+
ipAddress: i.ipAddress ?? null,
|
|
122
|
+
userAgent: i.userAgent ?? null
|
|
123
|
+
});
|
|
124
|
+
return yield* f(s, {
|
|
125
|
+
kind: "started",
|
|
126
|
+
at: u,
|
|
127
|
+
actorUserId: h.id,
|
|
128
|
+
actorEmail: h.email,
|
|
129
|
+
targetUserId: g.id,
|
|
130
|
+
targetEmail: g.email,
|
|
131
|
+
tenantId: g.tenantId,
|
|
132
|
+
sessionId: x,
|
|
133
|
+
grantId: S.id,
|
|
134
|
+
reason: _.length > 0 ? _ : null,
|
|
135
|
+
expiresAt: b,
|
|
136
|
+
ipAddress: i.ipAddress ?? null,
|
|
137
|
+
userAgent: i.userAgent ?? null
|
|
138
|
+
}), d(200, {
|
|
139
|
+
ok: !0,
|
|
140
|
+
impersonation: C,
|
|
141
|
+
subject: w.subject
|
|
142
|
+
}, { setCookie: w.setCookie });
|
|
143
|
+
}), m = (e, t, r) => n.gen(function* () {
|
|
144
|
+
let i = r.impersonation, a = (r.now ?? Date.now)(), o = new Date(a), s = c(e.subject);
|
|
145
|
+
if (!s || !e.sessionId) return d(400, { error: "not_impersonating" });
|
|
146
|
+
let l = yield* t.findImpersonationGrantBySession(e.sessionId);
|
|
147
|
+
yield* t.endImpersonationGrant(e.sessionId, "stopped", o).pipe(n.ignore), e.subject.type === "user" && (yield* t.revokeSession(e.subject.id, e.sessionId).pipe(n.ignore)), r.invalidateSession?.(e.sessionId);
|
|
148
|
+
let u = yield* t.findById(s.actorUserId), p = yield* t.findSession(s.actorSessionId), m = p ? Math.floor((p.expiresAt.getTime() - a) / 1e3) : 0;
|
|
149
|
+
if (yield* f(i, {
|
|
150
|
+
kind: "stopped",
|
|
151
|
+
at: o,
|
|
152
|
+
actorUserId: s.actorUserId,
|
|
153
|
+
actorEmail: u?.email ?? null,
|
|
154
|
+
targetUserId: e.subject.type === "user" ? e.subject.id : null,
|
|
155
|
+
targetEmail: null,
|
|
156
|
+
tenantId: l?.tenantId ?? e.subject.tenantId ?? null,
|
|
157
|
+
sessionId: e.sessionId,
|
|
158
|
+
...l ? { grantId: l.id } : {},
|
|
159
|
+
reason: l?.reason ?? null
|
|
160
|
+
}), !u || !p || m <= 0) return d(200, {
|
|
161
|
+
ok: !0,
|
|
162
|
+
restored: !1
|
|
163
|
+
}, { setCookie: r.issuer.clear() });
|
|
164
|
+
let h = yield* r.issuer.reissue(u, {
|
|
165
|
+
sessionId: s.actorSessionId,
|
|
166
|
+
ttlSeconds: m
|
|
167
|
+
});
|
|
168
|
+
return d(200, {
|
|
169
|
+
ok: !0,
|
|
170
|
+
restored: !0,
|
|
171
|
+
subject: h.subject
|
|
172
|
+
}, { setCookie: h.setCookie });
|
|
173
|
+
});
|
|
174
|
+
//#endregion
|
|
175
|
+
export { a as IMPERSONATION_DEFAULT_DURATION_S, o as IMPERSONATION_FORBIDDEN_SUBPATHS, p as handleImpersonationStart, m as handleImpersonationStop, u as impersonationAuditRedactor, c as impersonationOf, l as isImpersonated };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Effect } from 'effect';
|
|
|
5
5
|
import { KeyedSecret } from '@voltro/protocol/session';
|
|
6
6
|
import { MembershipsSource } from '@voltro/protocol';
|
|
7
7
|
import { SessionSecrets } from '@voltro/protocol/session';
|
|
8
|
-
import { SqlClient } from '
|
|
8
|
+
import { SqlClient } from 'effect/sql/SqlClient';
|
|
9
9
|
import { Subject } from '@voltro/protocol';
|
|
10
10
|
import { SubjectIdentity } from '@voltro/protocol';
|
|
11
11
|
import { VerifyOptions } from '@voltro/protocol/session';
|
|
@@ -310,7 +310,7 @@ export declare const dataStoreChallengeStore: (sql: SqlClient) => ChallengeStore
|
|
|
310
310
|
* and writes go through the store the framework opened, so there is no second
|
|
311
311
|
* pool and no second schema.
|
|
312
312
|
*/
|
|
313
|
-
export declare const dataStoreSessionRegistry: (store: DataStore) => SessionRegistry;
|
|
313
|
+
export declare const dataStoreSessionRegistry: (store: DataStore, options?: SessionRegistryStoreOptions) => SessionRegistry;
|
|
314
314
|
|
|
315
315
|
/** Default set of roles allowed to invite at all. */
|
|
316
316
|
export declare const DEFAULT_INVITER_ROLES: ReadonlyArray<string>;
|
|
@@ -1234,11 +1234,6 @@ export declare interface IssuedSession {
|
|
|
1234
1234
|
readonly setCookie: string;
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
1237
|
-
/**
|
|
1238
|
-
* `issueSession` with a row behind it: writes the session to the registry,
|
|
1239
|
-
* stamps its id into the cookie and signs. A cookie issued here can be killed
|
|
1240
|
-
* before its `exp`; one issued with plain `issueSession` cannot.
|
|
1241
|
-
*/
|
|
1242
1237
|
export declare const issueRegisteredSession: (registry: Pick<SessionRegistry, "insertSession">, subject: Subject | SubjectIdentity, secret: SessionSecretInput, options?: IssueRegisteredSessionOptions) => Promise<IssuedRegisteredSession>;
|
|
1243
1238
|
|
|
1244
1239
|
export declare interface IssueRegisteredSessionOptions extends IssueSessionOptions {
|
|
@@ -1799,6 +1794,9 @@ export declare interface RecoveryCodeRecord {
|
|
|
1799
1794
|
readonly createdAt: Date;
|
|
1800
1795
|
}
|
|
1801
1796
|
|
|
1797
|
+
export declare interface RegisteredSessionRotationOptions extends IssueRegisteredSessionOptions, SessionRegistryStoreOptions {
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1802
1800
|
export declare type RegistrationOutcome = {
|
|
1803
1801
|
readonly ok: true;
|
|
1804
1802
|
readonly result: RegistrationResult;
|
|
@@ -1846,6 +1844,11 @@ declare interface RevokeSessionInput {
|
|
|
1846
1844
|
readonly sessionId: string;
|
|
1847
1845
|
}
|
|
1848
1846
|
|
|
1847
|
+
/** Rotate one live credential atomically. Retained inactive aliases let
|
|
1848
|
+
* sign-out using an earlier signed cookie revoke this device's successor.
|
|
1849
|
+
* The caller must separately validate current account and tenant authority. */
|
|
1850
|
+
export declare const rotateRegisteredSession: (store: DataStore, subject: Subject | SubjectIdentity, presentedSessionId: string, secret: SessionSecretInput, options?: RegisteredSessionRotationOptions) => Promise<IssuedRegisteredSession | null>;
|
|
1851
|
+
|
|
1849
1852
|
/**
|
|
1850
1853
|
* Run the guards in order against `user`, short-circuiting on the FIRST
|
|
1851
1854
|
* rejection. With no guards (or all allowing) it resolves `{ ok: true }`.
|
|
@@ -1891,6 +1894,10 @@ export declare const sessionIdOfSubject: (subject: Subject) => string | null;
|
|
|
1891
1894
|
/** A server-side session row — enumerated so apps can list active
|
|
1892
1895
|
* devices + revoke them. Written on sign-in; deleted on revoke. */
|
|
1893
1896
|
export declare interface SessionRecord {
|
|
1897
|
+
/** Stable device lineage. The original row is its transaction lock anchor. */
|
|
1898
|
+
readonly familyId?: string | null;
|
|
1899
|
+
/** A replaced credential remains an inert revocation alias until the device ends. */
|
|
1900
|
+
readonly rotatedAt?: Date | null;
|
|
1894
1901
|
readonly id: string;
|
|
1895
1902
|
readonly userId: string;
|
|
1896
1903
|
readonly tenantId: string;
|
|
@@ -1905,6 +1912,11 @@ export declare interface SessionRecord {
|
|
|
1905
1912
|
* `UserStore`, usable without the rest of it. */
|
|
1906
1913
|
export declare type SessionRegistry = SessionStore;
|
|
1907
1914
|
|
|
1915
|
+
export declare interface SessionRegistryStoreOptions {
|
|
1916
|
+
/** Borrow an already owned transaction; otherwise registry writes open one. */
|
|
1917
|
+
readonly transaction?: 'existing';
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1908
1920
|
export declare interface SessionRevocationChecker {
|
|
1909
1921
|
/** Is this session still live? `false` means the row was revoked (or
|
|
1910
1922
|
* is past its `expiresAt`) — the caller rejects the cookie. Verdicts
|
package/dist/index.js
CHANGED
|
@@ -1,89 +1,23 @@
|
|
|
1
1
|
import { PasswordEmptyError as e, PasswordHashError as t, hashPassword as n, needsRehash as r, parseScryptParams as i, verifyPassword as a, verifyPasswordWithRehash as o } from "./password.js";
|
|
2
|
-
import { SESSION_COOKIE_NAME as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
listSessions: (t) => J.promise(async () => (await Q(e, Y("userId", t))).map(Z).sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime())),
|
|
25
|
-
insertSession: (t) => J.promise(async () => {
|
|
26
|
-
let n = /* @__PURE__ */ new Date(), r = {
|
|
27
|
-
...t,
|
|
28
|
-
createdAt: n,
|
|
29
|
-
lastSeenAt: n
|
|
30
|
-
};
|
|
31
|
-
return await e.insert(X, r), r;
|
|
32
|
-
}),
|
|
33
|
-
findSession: (t) => J.promise(async () => {
|
|
34
|
-
let n = (await Q(e, Y("id", t)))[0];
|
|
35
|
-
return n === void 0 ? null : Z(n);
|
|
36
|
-
}),
|
|
37
|
-
touchSession: (t, n) => J.promise(async () => {
|
|
38
|
-
await e.update(X, t, {
|
|
39
|
-
expiresAt: n,
|
|
40
|
-
lastSeenAt: /* @__PURE__ */ new Date()
|
|
41
|
-
});
|
|
42
|
-
}),
|
|
43
|
-
revokeSession: (t, n) => J.promise(async () => {
|
|
44
|
-
let r = (await Q(e, Y("id", n)))[0];
|
|
45
|
-
return r === void 0 || r.userId !== t ? !1 : e.delete(X, n);
|
|
46
|
-
}),
|
|
47
|
-
revokeAllOtherSessions: (t, n) => J.promise(async () => {
|
|
48
|
-
let r = 0;
|
|
49
|
-
for (let i of await Q(e, Y("userId", t))) i.id !== n && await e.delete(X, i.id) && (r += 1);
|
|
50
|
-
return r;
|
|
51
|
-
}),
|
|
52
|
-
revokeAllSessions: (t) => J.promise(async () => {
|
|
53
|
-
let n = 0;
|
|
54
|
-
for (let r of await Q(e, Y("userId", t))) await e.delete(X, r.id) && (n += 1);
|
|
55
|
-
return n;
|
|
56
|
-
})
|
|
57
|
-
}), $ = (e, t) => e.type === "user" ? {
|
|
58
|
-
...e,
|
|
59
|
-
metadata: {
|
|
60
|
-
...e.metadata ?? {},
|
|
61
|
-
sessionId: t
|
|
62
|
-
}
|
|
63
|
-
} : e, pt = async (e, t, n, r = {}) => {
|
|
64
|
-
if (t.type !== "user") throw Error("issueRegisteredSession: only a user subject has a session row");
|
|
65
|
-
let i = l("sess"), a = r.ttlSeconds ?? dt, o = (r.now ?? Date.now)();
|
|
66
|
-
await J.runPromise(e.insertSession({
|
|
67
|
-
id: i,
|
|
68
|
-
userId: t.id,
|
|
69
|
-
tenantId: t.tenantId ?? "",
|
|
70
|
-
expiresAt: new Date(o + a * 1e3),
|
|
71
|
-
ipAddress: r.ipAddress ?? null,
|
|
72
|
-
userAgent: r.userAgent ?? null
|
|
73
|
-
}));
|
|
74
|
-
let { ipAddress: ee, userAgent: te, ...c } = r;
|
|
75
|
-
return {
|
|
76
|
-
...s($(t, i), n, c),
|
|
77
|
-
sessionId: i
|
|
78
|
-
};
|
|
79
|
-
}, mt = (e) => async (t) => t.type === "user" ? (await J.runPromise(e.listMemberships(t.id))).map((e) => ({
|
|
80
|
-
tenantId: e.tenantId,
|
|
81
|
-
role: e.role
|
|
82
|
-
})) : [], ht = (e) => (t) => J.runPromise(e.send({
|
|
83
|
-
to: t.to,
|
|
84
|
-
subject: t.subject,
|
|
85
|
-
html: t.html,
|
|
86
|
-
text: t.text
|
|
87
|
-
}).pipe(J.asVoid, J.orElseSucceed(() => void 0)));
|
|
88
|
-
//#endregion
|
|
89
|
-
export { it as CSRF_COOKIE_NAME, at as CSRF_HEADER_NAME, _e as DEFAULT_INVITER_ROLES, W as DEFAULT_ROLE_RANK, me as EMAIL_NOT_VERIFIED_CODE, Ve as EMAIL_VERIFIED_METADATA_KEY, Ae as EMAIL_VERIFY_RESEND_COOLDOWN_S, Xe as EMAIL_VERIFY_TTL_S, Te as IMPERSONATION_DEFAULT_DURATION_S, He as IMPERSONATION_FORBIDDEN_SUBPATHS, We as INVITATION_MAX_PENDING, Je as INVITATION_TTL_S, Ze as MAGIC_LINK_TTL_S, Qe as MFA_PENDING_TTL_S, $e as NATIVE_CODE_TTL_S, Ie as NATIVE_PENDING_COOKIE, et as PASSWORD_RESET_TTL_S, e as PasswordEmptyError, t as PasswordHashError, ee as SESSION_COOKIE_NAME, Ue as TotpVerifyError, f as UserAlreadyExistsError, m as UserNotFoundError, G as appUserStore, mt as authMemberships, Be as authRoutesPlugin, te as clearSessionCookie, j as configSessionSecrets, x as dataStoreChallengeStore, ft as dataStoreSessionRegistry, le as defaultCanGrantRole, Le as emailVerificationActive, Se as emailVerificationGuard, ct as generateChallenge, pe as generateRecoveryCodes, ge as generateTotpCode, Pe as generateTotpSecret, ke as getAuthSessionStrategy, N as guardSubject, qe as handleAcceptInvitation, I as handleAcceptInvitationWithSignUp, b as handleCreateInvitation, C as handleCsrf, ce as handleEmailVerifyConfirm, Me as handleEmailVerifyRequest, fe as handleImpersonationStart, he as handleImpersonationStop, Ke as handleListInvitations, E as handleListMemberships, k as handleListSessions, F as handleMagicLinkConsume, Ye as handleMagicLinkRequest, y as handleMfaEnrollStart, z as handleMfaEnrollVerify, T as handleMfaRegenerateRecoveryCodes, R as handleMfaUnenroll, B as handleMfaVerify, xe as handleNativeAuthorize, K as handleNativeComplete, je as handleNativeExchange, M as handlePasskeyAssertOptions, we as handlePasskeyAssertVerify, v as handlePasskeyRegisterOptions, Ce as handlePasskeyRegisterVerify, w as handlePasswordResetConfirm, O as handlePasswordResetRequest, L as handlePreviewInvitation, Fe as handleRevokeAllOtherSessions, S as handleRevokeInvitation, D as handleRevokeSession, H as handleSignIn, V as handleSignOut, U as handleSignUp, P as handleSwitchTenant, n as hashPassword, tt as hashToken, Ne as impersonationAuditRedactor, De as impersonationOf, Re as isAllowedRedirectUri, ot as isCsrfTokenWellFormed, ze as isEmailVerified, ye as isImpersonated, st as issueCsrfToken, pt as issueRegisteredSession, s as issueSession, _ as issueUserSession, ht as mailSender, d as makeSessionRevocationChecker, A as memoryChallengeStore, u as memoryUserStore, nt as mintSecret, rt as mintToken, ue as nativeLoginRedirect, r as needsRehash, l as newAuthId, Oe as otpauthUrl, i as parseScryptParams, Ge as postgresUserStore, c as readSession, ne as readSessionFromHeaders, re as readSessionKeyed, ie as resolveSessionSecret, ae as resolveSessionSecrets, ve as runSubjectGuards, de as sendEmailVerification, p as sessionIdOfSubject, oe as sessionSecretsOf, h as subjectFromUser, se as subjectMemberships, Ee as userEmailVerified, lt as verifyAssertion, q as verifyCsrf, a as verifyPassword, o as verifyPasswordWithRehash, ut as verifyRegistration, be as verifyTotpCode, g as voltroPasswordStrategy, $ as withSessionId };
|
|
2
|
+
import { SESSION_COOKIE_NAME as s, clearSessionCookie as c, issueSession as l, readSession as u, readSessionFromHeaders as d, readSessionKeyed as f, resolveSessionSecret as p, resolveSessionSecrets as m, sessionSecretsOf as h } from "./session.js";
|
|
3
|
+
import { makeSessionRevocationChecker as g } from "./revocation.js";
|
|
4
|
+
import { UserAlreadyExistsError as _, UserNotFoundError as v, memoryUserStore as y, newAuthId as b, sessionIdOfSubject as x, subjectFromUser as S, subjectMemberships as C } from "./userStore.js";
|
|
5
|
+
import { dataStoreSessionRegistry as w, issueRegisteredSession as T, rotateRegisteredSession as E, withSessionId as D } from "./sessionRegistry.js";
|
|
6
|
+
import { authMemberships as O } from "./memberships.js";
|
|
7
|
+
import { postgresUserStore as k } from "./postgresUserStore.js";
|
|
8
|
+
import { appUserStore as A } from "./appUserStore.js";
|
|
9
|
+
import { runSubjectGuards as j } from "./subjectGuard.js";
|
|
10
|
+
import { TotpVerifyError as M, generateRecoveryCodes as N, generateTotpCode as P, generateTotpSecret as F, otpauthUrl as I, verifyTotpCode as L } from "./totp.js";
|
|
11
|
+
import { EMAIL_VERIFY_TTL_S as R, MAGIC_LINK_TTL_S as z, MFA_PENDING_TTL_S as B, NATIVE_CODE_TTL_S as V, PASSWORD_RESET_TTL_S as H, hashToken as U, mintSecret as W, mintToken as G } from "./tokens.js";
|
|
12
|
+
import { CSRF_COOKIE_NAME as K, CSRF_HEADER_NAME as q, isCsrfTokenWellFormed as J, issueCsrfToken as Y, verifyCsrf as X } from "./csrf.js";
|
|
13
|
+
import { EMAIL_NOT_VERIFIED_CODE as Z, EMAIL_VERIFIED_METADATA_KEY as Q, EMAIL_VERIFY_RESEND_COOLDOWN_S as $, emailVerificationActive as ee, emailVerificationGuard as te, handleEmailVerifyConfirm as ne, handleEmailVerifyRequest as re, isEmailVerified as ie, sendEmailVerification as ae, userEmailVerified as oe } from "./emailVerification.js";
|
|
14
|
+
import { configSessionSecrets as se, guardSubject as ce, handleCsrf as le, handleListMemberships as ue, handleListSessions as de, handleMagicLinkConsume as fe, handleMagicLinkRequest as pe, handleMfaEnrollStart as me, handleMfaEnrollVerify as he, handleMfaRegenerateRecoveryCodes as ge, handleMfaUnenroll as _e, handleMfaVerify as ve, handlePasswordResetConfirm as ye, handlePasswordResetRequest as be, handleRevokeAllOtherSessions as xe, handleRevokeSession as Se, handleSignIn as Ce, handleSignOut as we, handleSignUp as Te, handleSwitchTenant as Ee, issueUserSession as De } from "./handlers.js";
|
|
15
|
+
import { generateChallenge as Oe, verifyAssertion as ke, verifyRegistration as Ae } from "./webauthn.js";
|
|
16
|
+
import { dataStoreChallengeStore as je, handlePasskeyAssertOptions as Me, handlePasskeyAssertVerify as Ne, handlePasskeyRegisterOptions as Pe, handlePasskeyRegisterVerify as Fe, memoryChallengeStore as Ie } from "./passkeyHandlers.js";
|
|
17
|
+
import { DEFAULT_INVITER_ROLES as Le, DEFAULT_ROLE_RANK as Re, INVITATION_MAX_PENDING as ze, INVITATION_TTL_S as Be, defaultCanGrantRole as Ve, handleAcceptInvitation as He, handleAcceptInvitationWithSignUp as Ue, handleCreateInvitation as We, handleListInvitations as Ge, handlePreviewInvitation as Ke, handleRevokeInvitation as qe } from "./invitations.js";
|
|
18
|
+
import { IMPERSONATION_DEFAULT_DURATION_S as Je, IMPERSONATION_FORBIDDEN_SUBPATHS as Ye, handleImpersonationStart as Xe, handleImpersonationStop as Ze, impersonationAuditRedactor as Qe, impersonationOf as $e, isImpersonated as et } from "./impersonation.js";
|
|
19
|
+
import { NATIVE_PENDING_COOKIE as tt, handleNativeAuthorize as nt, handleNativeComplete as rt, handleNativeExchange as it, isAllowedRedirectUri as at, nativeLoginRedirect as ot } from "./nativeRoutes.js";
|
|
20
|
+
import { voltroPasswordStrategy as st } from "./strategy.js";
|
|
21
|
+
import { authRoutesPlugin as ct, getAuthSessionStrategy as lt } from "./plugin.js";
|
|
22
|
+
import { mailSender as ut } from "./mail.js";
|
|
23
|
+
export { K as CSRF_COOKIE_NAME, q as CSRF_HEADER_NAME, Le as DEFAULT_INVITER_ROLES, Re as DEFAULT_ROLE_RANK, Z as EMAIL_NOT_VERIFIED_CODE, Q as EMAIL_VERIFIED_METADATA_KEY, $ as EMAIL_VERIFY_RESEND_COOLDOWN_S, R as EMAIL_VERIFY_TTL_S, Je as IMPERSONATION_DEFAULT_DURATION_S, Ye as IMPERSONATION_FORBIDDEN_SUBPATHS, ze as INVITATION_MAX_PENDING, Be as INVITATION_TTL_S, z as MAGIC_LINK_TTL_S, B as MFA_PENDING_TTL_S, V as NATIVE_CODE_TTL_S, tt as NATIVE_PENDING_COOKIE, H as PASSWORD_RESET_TTL_S, e as PasswordEmptyError, t as PasswordHashError, s as SESSION_COOKIE_NAME, M as TotpVerifyError, _ as UserAlreadyExistsError, v as UserNotFoundError, A as appUserStore, O as authMemberships, ct as authRoutesPlugin, c as clearSessionCookie, se as configSessionSecrets, je as dataStoreChallengeStore, w as dataStoreSessionRegistry, Ve as defaultCanGrantRole, ee as emailVerificationActive, te as emailVerificationGuard, Oe as generateChallenge, N as generateRecoveryCodes, P as generateTotpCode, F as generateTotpSecret, lt as getAuthSessionStrategy, ce as guardSubject, He as handleAcceptInvitation, Ue as handleAcceptInvitationWithSignUp, We as handleCreateInvitation, le as handleCsrf, ne as handleEmailVerifyConfirm, re as handleEmailVerifyRequest, Xe as handleImpersonationStart, Ze as handleImpersonationStop, Ge as handleListInvitations, ue as handleListMemberships, de as handleListSessions, fe as handleMagicLinkConsume, pe as handleMagicLinkRequest, me as handleMfaEnrollStart, he as handleMfaEnrollVerify, ge as handleMfaRegenerateRecoveryCodes, _e as handleMfaUnenroll, ve as handleMfaVerify, nt as handleNativeAuthorize, rt as handleNativeComplete, it as handleNativeExchange, Me as handlePasskeyAssertOptions, Ne as handlePasskeyAssertVerify, Pe as handlePasskeyRegisterOptions, Fe as handlePasskeyRegisterVerify, ye as handlePasswordResetConfirm, be as handlePasswordResetRequest, Ke as handlePreviewInvitation, xe as handleRevokeAllOtherSessions, qe as handleRevokeInvitation, Se as handleRevokeSession, Ce as handleSignIn, we as handleSignOut, Te as handleSignUp, Ee as handleSwitchTenant, n as hashPassword, U as hashToken, Qe as impersonationAuditRedactor, $e as impersonationOf, at as isAllowedRedirectUri, J as isCsrfTokenWellFormed, ie as isEmailVerified, et as isImpersonated, Y as issueCsrfToken, T as issueRegisteredSession, l as issueSession, De as issueUserSession, ut as mailSender, g as makeSessionRevocationChecker, Ie as memoryChallengeStore, y as memoryUserStore, W as mintSecret, G as mintToken, ot as nativeLoginRedirect, r as needsRehash, b as newAuthId, I as otpauthUrl, i as parseScryptParams, k as postgresUserStore, u as readSession, d as readSessionFromHeaders, f as readSessionKeyed, p as resolveSessionSecret, m as resolveSessionSecrets, E as rotateRegisteredSession, j as runSubjectGuards, ae as sendEmailVerification, x as sessionIdOfSubject, h as sessionSecretsOf, S as subjectFromUser, C as subjectMemberships, oe as userEmailVerified, ke as verifyAssertion, X as verifyCsrf, a as verifyPassword, o as verifyPasswordWithRehash, Ae as verifyRegistration, L as verifyTotpCode, st as voltroPasswordStrategy, D as withSessionId };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { newAuthId as e } from "./userStore.js";
|
|
2
|
+
import { hashToken as t, mintSecret as n } from "./tokens.js";
|
|
3
|
+
import { decideAdmission as r } from "./admission.js";
|
|
4
|
+
import { Effect as i } from "effect";
|
|
5
|
+
//#region src/invitations.ts
|
|
6
|
+
var a = 604800, o = 500, s = [
|
|
7
|
+
"owner",
|
|
8
|
+
"admin",
|
|
9
|
+
"member",
|
|
10
|
+
"viewer"
|
|
11
|
+
], c = ["owner", "admin"], l = (e, t) => t.indexOf(e), u = (e, t = s) => {
|
|
12
|
+
let n = l(e.inviterRole, t);
|
|
13
|
+
if (n < 0) return !1;
|
|
14
|
+
let r = l(e.requestedRole, t);
|
|
15
|
+
return r < 0 ? n === 0 : r >= n;
|
|
16
|
+
}, d = (e) => {
|
|
17
|
+
let t = e?.roleRank ?? s;
|
|
18
|
+
return e?.canGrantRole ?? ((e) => u(e, t));
|
|
19
|
+
}, f = (e, t, n = {}) => ({
|
|
20
|
+
status: e,
|
|
21
|
+
body: JSON.stringify(t),
|
|
22
|
+
contentType: "application/json; charset=utf-8",
|
|
23
|
+
...n
|
|
24
|
+
}), p = (e, t, n) => `${e}${t}${t.includes("?") ? "&" : "?"}token=${encodeURIComponent(n)}`, m = (e) => ({
|
|
25
|
+
id: e.id,
|
|
26
|
+
email: e.email,
|
|
27
|
+
tenantId: e.tenantId,
|
|
28
|
+
role: e.role,
|
|
29
|
+
invitedByUserId: e.invitedByUserId,
|
|
30
|
+
expiresAt: e.expiresAt.toISOString(),
|
|
31
|
+
acceptedAt: e.acceptedAt?.toISOString() ?? null,
|
|
32
|
+
revokedAt: e.revokedAt?.toISOString() ?? null,
|
|
33
|
+
createdAt: e.createdAt.toISOString()
|
|
34
|
+
}), h = (e, t, n, r) => i.gen(function* () {
|
|
35
|
+
let i = yield* n.membershipRole(e, t);
|
|
36
|
+
return i === null || !(r?.inviterRoles ?? c).includes(i) ? f(403, { error: "not_permitted" }) : { role: i };
|
|
37
|
+
}), g = (e) => "status" in e, _ = (t, r, a) => i.gen(function* () {
|
|
38
|
+
let o = t.email.trim().toLowerCase();
|
|
39
|
+
if (!o || !o.includes("@")) return f(400, { error: "a valid email is required" });
|
|
40
|
+
if (!t.tenantId) return f(400, { error: "tenantId required" });
|
|
41
|
+
if (!t.role) return f(400, { error: "role required" });
|
|
42
|
+
let s = a.invitations, c = yield* h(t.inviterUserId, t.tenantId, r, s);
|
|
43
|
+
if (g(c)) return c;
|
|
44
|
+
if (!d(s)({
|
|
45
|
+
inviterRole: c.role,
|
|
46
|
+
requestedRole: t.role,
|
|
47
|
+
tenantId: t.tenantId
|
|
48
|
+
})) return f(403, {
|
|
49
|
+
error: "role_not_grantable",
|
|
50
|
+
message: `A '${c.role}' may not invite a '${t.role}'.`
|
|
51
|
+
});
|
|
52
|
+
let l = new Date((a.now ?? Date.now)());
|
|
53
|
+
if ((yield* r.countPendingInvitations(t.tenantId, l)) >= (s?.maxPending ?? 500)) return f(429, { error: "too_many_pending_invitations" });
|
|
54
|
+
yield* r.supersedePendingInvitations(t.tenantId, o, t.inviterUserId, l).pipe(i.ignore);
|
|
55
|
+
let u = s?.ttlSeconds ?? 604800, _ = n(u), v = yield* r.insertInvitation({
|
|
56
|
+
id: e("inv"),
|
|
57
|
+
tokenHash: _.tokenHash,
|
|
58
|
+
email: o,
|
|
59
|
+
tenantId: t.tenantId,
|
|
60
|
+
role: t.role,
|
|
61
|
+
invitedByUserId: t.inviterUserId,
|
|
62
|
+
expiresAt: new Date(l.getTime() + u * 1e3)
|
|
63
|
+
});
|
|
64
|
+
if (a.sendEmail) {
|
|
65
|
+
let e = p(a.appBaseUrl ?? "", s?.callbackPath ?? "/auth/invitations/accept", _.token);
|
|
66
|
+
yield* i.tryPromise(() => a.sendEmail({
|
|
67
|
+
to: o,
|
|
68
|
+
kind: "invitation",
|
|
69
|
+
actionUrl: e,
|
|
70
|
+
subject: "You have been invited",
|
|
71
|
+
text: `You have been invited to join as ${t.role}: ${e}`,
|
|
72
|
+
html: `<p>You have been invited to join as <strong>${t.role}</strong>. <a href="${e}">Accept the invitation</a> — this link expires.</p>`
|
|
73
|
+
})).pipe(i.ignore);
|
|
74
|
+
}
|
|
75
|
+
return f(201, {
|
|
76
|
+
ok: !0,
|
|
77
|
+
invitation: m(v)
|
|
78
|
+
});
|
|
79
|
+
}), v = (e, t, n) => i.gen(function* () {
|
|
80
|
+
let r = yield* h(e.callerUserId, e.tenantId, t, n.invitations);
|
|
81
|
+
return g(r) ? r : f(200, { invitations: (yield* t.listInvitations(e.tenantId)).map(m) });
|
|
82
|
+
}), y = (e, t, n) => i.gen(function* () {
|
|
83
|
+
if (!e.invitationId) return f(400, { error: "invitationId required" });
|
|
84
|
+
let r = yield* h(e.callerUserId, e.tenantId, t, n.invitations);
|
|
85
|
+
if (g(r)) return r;
|
|
86
|
+
let i = new Date((n.now ?? Date.now)());
|
|
87
|
+
return (yield* t.revokeInvitation(e.invitationId, e.tenantId, e.callerUserId, i)) ? f(200, {
|
|
88
|
+
ok: !0,
|
|
89
|
+
revoked: 1
|
|
90
|
+
}) : f(404, { error: "invitation_not_found" });
|
|
91
|
+
}), b = (e, n, r = {}) => i.gen(function* () {
|
|
92
|
+
if (!e.token) return f(400, { error: "token required" });
|
|
93
|
+
let i = yield* n.findInvitationByHash(t(e.token));
|
|
94
|
+
if (!i) return f(404, { error: "invalid_invitation" });
|
|
95
|
+
let a = new Date((r.now ?? Date.now)());
|
|
96
|
+
return f(200, {
|
|
97
|
+
ok: !0,
|
|
98
|
+
state: i.acceptedAt === null ? i.revokedAt === null ? i.expiresAt.getTime() <= a.getTime() ? "expired" : "pending" : "revoked" : "accepted",
|
|
99
|
+
email: i.email,
|
|
100
|
+
tenantId: i.tenantId,
|
|
101
|
+
role: i.role,
|
|
102
|
+
expiresAt: i.expiresAt.toISOString()
|
|
103
|
+
});
|
|
104
|
+
}), x = (e, n, a = {}) => i.gen(function* () {
|
|
105
|
+
if (!e.token) return f(400, { error: "token required" });
|
|
106
|
+
let o = t(e.token), s = new Date((a.now ?? Date.now)()), c = yield* n.findInvitationByHash(o);
|
|
107
|
+
if (!c) return f(401, { error: "invalid_or_expired_invitation" });
|
|
108
|
+
if (!e.userId) return f(401, {
|
|
109
|
+
error: "authentication_required",
|
|
110
|
+
email: c.email
|
|
111
|
+
});
|
|
112
|
+
let l = yield* n.findById(e.userId);
|
|
113
|
+
if (!l) return f(401, {
|
|
114
|
+
error: "authentication_required",
|
|
115
|
+
email: c.email
|
|
116
|
+
});
|
|
117
|
+
if (l.email.toLowerCase() !== c.email.toLowerCase()) return f(403, {
|
|
118
|
+
error: "invitation_email_mismatch",
|
|
119
|
+
message: "This invitation was issued to a different email address. Sign in as that address to accept it."
|
|
120
|
+
});
|
|
121
|
+
let u = yield* r(a.admit, {
|
|
122
|
+
email: c.email,
|
|
123
|
+
tenantId: c.tenantId,
|
|
124
|
+
via: "invitation"
|
|
125
|
+
});
|
|
126
|
+
if (u) return f(403, {
|
|
127
|
+
error: "not_admitted",
|
|
128
|
+
reason: u.reason,
|
|
129
|
+
message: u.message
|
|
130
|
+
});
|
|
131
|
+
let d = yield* n.acceptInvitation(o, l.id, s);
|
|
132
|
+
if (!d) return f(401, { error: "invalid_or_expired_invitation" });
|
|
133
|
+
let p = yield* n.addMembership({
|
|
134
|
+
userId: l.id,
|
|
135
|
+
tenantId: d.tenantId,
|
|
136
|
+
role: d.role
|
|
137
|
+
});
|
|
138
|
+
return yield* n.markEmailVerified(l.id).pipe(i.ignore), f(200, {
|
|
139
|
+
ok: !0,
|
|
140
|
+
membership: {
|
|
141
|
+
tenantId: p.tenantId,
|
|
142
|
+
role: p.role,
|
|
143
|
+
joinedAt: p.joinedAt.toISOString()
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}), S = (n, a, o) => i.gen(function* () {
|
|
147
|
+
if (!n.token || !n.password) return f(400, { error: "token and password required" });
|
|
148
|
+
if (n.password.length < (o.minPasswordLength ?? 8)) return f(400, { error: "password must be at least 8 characters" });
|
|
149
|
+
let s = t(n.token), c = new Date((o.now ?? Date.now)()), l = yield* a.findInvitationByHash(s);
|
|
150
|
+
if (!l) return f(401, { error: "invalid_or_expired_invitation" });
|
|
151
|
+
if (yield* a.findByEmail(l.email)) return f(409, {
|
|
152
|
+
error: "account_exists",
|
|
153
|
+
message: "An account already exists for this address. Sign in, then accept the invitation."
|
|
154
|
+
});
|
|
155
|
+
let u = yield* o.hashPassword(n.password).pipe(i.orElseSucceed(() => ""));
|
|
156
|
+
if (!u) return f(500, { error: "hash_failed" });
|
|
157
|
+
let d = yield* r(o.admit, {
|
|
158
|
+
email: l.email,
|
|
159
|
+
tenantId: l.tenantId,
|
|
160
|
+
via: "invitation"
|
|
161
|
+
});
|
|
162
|
+
if (d) return f(403, {
|
|
163
|
+
error: "not_admitted",
|
|
164
|
+
reason: d.reason,
|
|
165
|
+
message: d.message
|
|
166
|
+
});
|
|
167
|
+
let p = e("user"), m = yield* a.acceptInvitation(s, p, c);
|
|
168
|
+
if (!m) return f(401, { error: "invalid_or_expired_invitation" });
|
|
169
|
+
let h = yield* a.insert({
|
|
170
|
+
id: p,
|
|
171
|
+
email: m.email,
|
|
172
|
+
passwordHash: u,
|
|
173
|
+
tenantId: m.tenantId
|
|
174
|
+
}).pipe(i.orElseSucceed(() => null));
|
|
175
|
+
return h ? (yield* a.addMembership({
|
|
176
|
+
userId: h.id,
|
|
177
|
+
tenantId: m.tenantId,
|
|
178
|
+
role: m.role
|
|
179
|
+
}), yield* a.markEmailVerified(h.id).pipe(i.ignore), f(201, {
|
|
180
|
+
ok: !0,
|
|
181
|
+
userId: h.id,
|
|
182
|
+
tenantId: m.tenantId,
|
|
183
|
+
role: m.role
|
|
184
|
+
})) : f(409, { error: "account_exists" });
|
|
185
|
+
});
|
|
186
|
+
//#endregion
|
|
187
|
+
export { c as DEFAULT_INVITER_ROLES, s as DEFAULT_ROLE_RANK, o as INVITATION_MAX_PENDING, a as INVITATION_TTL_S, u as defaultCanGrantRole, x as handleAcceptInvitation, S as handleAcceptInvitationWithSignUp, _ as handleCreateInvitation, v as handleListInvitations, b as handlePreviewInvitation, y as handleRevokeInvitation };
|
package/dist/mail.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
//#region src/mail.ts
|
|
3
|
+
var t = (t) => (n) => e.runPromise(t.send({
|
|
4
|
+
to: n.to,
|
|
5
|
+
subject: n.subject,
|
|
6
|
+
html: n.html,
|
|
7
|
+
text: n.text
|
|
8
|
+
}).pipe(e.asVoid, e.orElseSucceed(() => void 0)));
|
|
9
|
+
//#endregion
|
|
10
|
+
export { t as mailSender };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
//#region src/memberships.ts
|
|
3
|
+
var t = (t) => async (n) => n.type === "user" ? (await e.runPromise(t.listMemberships(n.id))).map((e) => ({
|
|
4
|
+
tenantId: e.tenantId,
|
|
5
|
+
role: e.role
|
|
6
|
+
})) : [];
|
|
7
|
+
//#endregion
|
|
8
|
+
export { t as authMemberships };
|