@shivaedev/platform 0.22.1 → 0.23.1
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 +14 -0
- package/README.md +42 -1
- package/dist/server/better-auth/allowedAccounts.d.ts +10 -0
- package/dist/server/better-auth/allowedAccounts.d.ts.map +1 -0
- package/dist/server/better-auth/allowedAccounts.js +20 -0
- package/dist/server/better-auth/allowedAccounts.js.map +1 -0
- package/dist/server/better-auth/mount.d.ts +192 -2
- package/dist/server/better-auth/mount.d.ts.map +1 -1
- package/dist/server/better-auth/mount.js +34 -2
- package/dist/server/better-auth/mount.js.map +1 -1
- package/dist/server/better-auth/revokeUnadmitted.d.ts +10 -0
- package/dist/server/better-auth/revokeUnadmitted.d.ts.map +1 -0
- package/dist/server/better-auth/revokeUnadmitted.js +29 -0
- package/dist/server/better-auth/revokeUnadmitted.js.map +1 -0
- package/dist/server/better-auth/signInGate.d.ts +37 -0
- package/dist/server/better-auth/signInGate.d.ts.map +1 -0
- package/dist/server/better-auth/signInGate.js +38 -0
- package/dist/server/better-auth/signInGate.js.map +1 -0
- package/dist/server/better-auth/signInPolicy.d.ts +193 -0
- package/dist/server/better-auth/signInPolicy.d.ts.map +1 -0
- package/dist/server/better-auth/signInPolicy.js +61 -0
- package/dist/server/better-auth/signInPolicy.js.map +1 -0
- package/dist/server/databaseLayer.d.ts.map +1 -1
- package/dist/server/databaseLayer.js +18 -1
- package/dist/server/databaseLayer.js.map +1 -1
- package/package.json +4 -3
- package/src/server/better-auth/allowedAccounts.ts +29 -0
- package/src/server/better-auth/mount.ts +49 -2
- package/src/server/better-auth/revokeUnadmitted.ts +41 -0
- package/src/server/better-auth/signInGate.ts +77 -0
- package/src/server/better-auth/signInPolicy.ts +72 -0
- package/src/server/databaseLayer.ts +20 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { BetterAuthOptions, ValidateUserInfoResult, ValidateUserInfoSource } from "better-auth";
|
|
2
|
+
import { APIError } from "better-auth/api";
|
|
3
|
+
import { Effect, Exit } from "effect";
|
|
4
|
+
import { fromStorage, type PolicyStorage, type SignInGate } from "#server/better-auth/signInGate.ts";
|
|
5
|
+
import { redact } from "#server/rpc-server/redact.ts";
|
|
6
|
+
|
|
7
|
+
type SessionCreateBefore = NonNullable<NonNullable<NonNullable<NonNullable<BetterAuthOptions["databaseHooks"]>["session"]>["create"]>["before"]>;
|
|
8
|
+
type ValidateUserInfo = NonNullable<NonNullable<BetterAuthOptions["user"]>["validateUserInfo"]>;
|
|
9
|
+
export type PolicyRun = <A, E>(effect: Effect.Effect<A, E>) => Promise<A>;
|
|
10
|
+
|
|
11
|
+
const REFUSED_MESSAGE = "This account may not sign in";
|
|
12
|
+
const UNAVAILABLE_MESSAGE = "Sign-in is temporarily unavailable";
|
|
13
|
+
const refused: ValidateUserInfoResult = { error: "sign_in_refused", errorDescription: REFUSED_MESSAGE };
|
|
14
|
+
const unavailable: ValidateUserInfoResult = { error: "sign_in_unavailable", errorDescription: UNAVAILABLE_MESSAGE };
|
|
15
|
+
|
|
16
|
+
function logged<A, E>(effect: Effect.Effect<A, E>) {
|
|
17
|
+
return Effect.tapCause(effect, (cause) => Effect.logError("Sign-in policy check failed", redact(cause)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function signingInAccount(
|
|
21
|
+
source: ValidateUserInfoSource,
|
|
22
|
+
providers: readonly { readonly accountSubject: (context: { profile: object; tokens: object }) => unknown; readonly id: string }[],
|
|
23
|
+
) {
|
|
24
|
+
const profile = source.oauth?.profile;
|
|
25
|
+
const provider = providers.find(({ id }) => id === source.oauth?.providerId);
|
|
26
|
+
if (source.method !== "oauth" || profile === undefined || provider === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const subject = await provider.accountSubject({ profile, tokens: {} });
|
|
30
|
+
return typeof subject === "string" || typeof subject === "number" ? { accountId: String(subject), providerId: provider.id } : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function signInPolicyOptions<E>(options: BetterAuthOptions, gate: SignInGate<E>, run: PolicyRun, storage: () => Promise<PolicyStorage>) {
|
|
34
|
+
const sessionBefore = options.databaseHooks?.session?.create?.before;
|
|
35
|
+
const before: SessionCreateBefore = async (session, endpoint) => {
|
|
36
|
+
const own = await sessionBefore?.(session, endpoint);
|
|
37
|
+
if (own === false) {
|
|
38
|
+
return own;
|
|
39
|
+
}
|
|
40
|
+
const userId = typeof own === "object" && typeof own.data.userId === "string" ? own.data.userId : session.userId;
|
|
41
|
+
const exit = await run(Effect.exit(logged(Effect.flatMap(fromStorage(storage), (auth) => gate.admitsUser(auth, userId)))));
|
|
42
|
+
if (Exit.isFailure(exit)) {
|
|
43
|
+
throw new APIError("SERVICE_UNAVAILABLE", { code: unavailable.error, message: UNAVAILABLE_MESSAGE });
|
|
44
|
+
}
|
|
45
|
+
if (!exit.value) {
|
|
46
|
+
throw new APIError("FORBIDDEN", { code: refused.error, message: REFUSED_MESSAGE });
|
|
47
|
+
}
|
|
48
|
+
return own;
|
|
49
|
+
};
|
|
50
|
+
const validateUserInfo: ValidateUserInfo = async (data, endpoint) => {
|
|
51
|
+
const own = await options.user?.validateUserInfo?.(data, endpoint);
|
|
52
|
+
if (own !== undefined || data.source.action !== "create-user") {
|
|
53
|
+
return own;
|
|
54
|
+
}
|
|
55
|
+
const account = await signingInAccount(data.source, endpoint.context.socialProviders).catch(() => undefined);
|
|
56
|
+
if (account === undefined) {
|
|
57
|
+
return refused;
|
|
58
|
+
}
|
|
59
|
+
const exit = await run(Effect.exit(logged(gate.admits([account]))));
|
|
60
|
+
if (Exit.isFailure(exit)) {
|
|
61
|
+
return unavailable;
|
|
62
|
+
}
|
|
63
|
+
return exit.value ? undefined : refused;
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
databaseHooks: {
|
|
67
|
+
...options.databaseHooks,
|
|
68
|
+
session: { ...options.databaseHooks?.session, create: { ...options.databaseHooks?.session?.create, before } },
|
|
69
|
+
},
|
|
70
|
+
user: { ...options.user, validateUserInfo },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
2
3
|
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
3
4
|
import { Config, Effect, Layer, Redacted, Schedule } from "effect";
|
|
4
5
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
@@ -21,11 +22,29 @@ export function databaseLayer<R>(options: AppDatabaseOptions<R>, changesService:
|
|
|
21
22
|
const pool = Layer.unwrap(
|
|
22
23
|
Effect.gen(function* () {
|
|
23
24
|
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
25
|
+
const address = databaseAddress(url);
|
|
26
|
+
const disconnected = new Set<Promise<void>>();
|
|
27
|
+
// Native pool shutdown sends Terminate without waiting for its sockets to close.
|
|
28
|
+
yield* Effect.addFinalizer(() => Effect.promise(() => Promise.all([...disconnected])));
|
|
24
29
|
return PgClient.layer({
|
|
30
|
+
stream: () => {
|
|
31
|
+
const target = new URL(Redacted.value(url));
|
|
32
|
+
const host = address.host ?? target.searchParams.getAll("host").at(-1) ?? (decodeURIComponent(target.hostname) || "localhost");
|
|
33
|
+
const port = address.port ?? 5432;
|
|
34
|
+
const socket = createConnection(host.startsWith("/") ? { path: `${host}/.s.PGSQL.${port}` } : { host, noDelay: true, port });
|
|
35
|
+
const closed = new Promise<void>((resolve) =>
|
|
36
|
+
socket.once("close", () => {
|
|
37
|
+
disconnected.delete(closed);
|
|
38
|
+
resolve();
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
disconnected.add(closed);
|
|
42
|
+
return socket;
|
|
43
|
+
},
|
|
25
44
|
transformQueryNames: options.config?.transformQueryNames,
|
|
26
45
|
transformResultNames: options.config?.transformResultNames,
|
|
27
46
|
url,
|
|
28
|
-
...
|
|
47
|
+
...address,
|
|
29
48
|
...(options.config?.maxConnections === undefined ? {} : { maxConnections: options.config.maxConnections }),
|
|
30
49
|
...(options.config?.applicationName === undefined ? {} : { applicationName: options.config.applicationName }),
|
|
31
50
|
});
|