@vonall/auth 0.0.0-stage → 0.1.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 +12 -0
- package/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +18 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/messages.d.ts +50 -0
- package/dist/messages.js +88 -0
- package/dist/permissions.d.ts +48 -0
- package/dist/permissions.js +17 -0
- package/dist/remote/app_user.remote.d.ts +66 -0
- package/dist/remote/app_user.remote.js +91 -0
- package/dist/remote/einladung.remote.d.ts +27 -0
- package/dist/remote/einladung.remote.js +56 -0
- package/dist/remote/index.d.ts +2 -0
- package/dist/remote/index.js +2 -0
- package/dist/schema/index.d.ts +3683 -0
- package/dist/schema/index.js +265 -0
- package/dist/server/create.d.ts +87 -0
- package/dist/server/create.js +158 -0
- package/dist/server/crypto.d.ts +5 -0
- package/dist/server/crypto.js +15 -0
- package/dist/server/helpers.d.ts +18 -0
- package/dist/server/helpers.js +62 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.js +9 -0
- package/dist/server/loads.d.ts +57 -0
- package/dist/server/loads.js +83 -0
- package/dist/server/registry.d.ts +39 -0
- package/dist/server/registry.js +15 -0
- package/dist/server/repositories/EinladungRepo.d.ts +36 -0
- package/dist/server/repositories/EinladungRepo.js +74 -0
- package/dist/server/repositories/UserRepo.d.ts +19 -0
- package/dist/server/repositories/UserRepo.js +67 -0
- package/dist/server/service/einladung.service.d.ts +48 -0
- package/dist/server/service/einladung.service.js +76 -0
- package/dist/server/service/konto.service.d.ts +7 -0
- package/dist/server/service/konto.service.js +11 -0
- package/dist/server/service/mail.d.ts +12 -0
- package/dist/server/service/mail.js +40 -0
- package/dist/templates/InvitationEmail.svelte +18 -0
- package/dist/templates/InvitationEmail.svelte.d.ts +10 -0
- package/dist/templates/ResetPasswordEmail.svelte +16 -0
- package/dist/templates/ResetPasswordEmail.svelte.d.ts +9 -0
- package/dist/templates/VerifyEmailEmail.svelte +16 -0
- package/dist/templates/VerifyEmailEmail.svelte.d.ts +9 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +26 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +21 -0
- package/dist/util/fresh.d.ts +11 -0
- package/dist/util/fresh.js +20 -0
- package/dist/util/messageOf.d.ts +5 -0
- package/dist/util/messageOf.js +12 -0
- package/dist/util/names.d.ts +10 -0
- package/dist/util/names.js +19 -0
- package/dist/util/sameSite.d.ts +5 -0
- package/dist/util/sameSite.js +15 -0
- package/dist/util/signup.d.ts +15 -0
- package/dist/util/signup.js +23 -0
- package/package.json +96 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @vonall/auth
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- schema: the first release, the server side: `createAuth()` (better-auth with the signup hooks, the Einladung, the mails, Admin as an option), `authSchema()` with the tables (`user`, `session`, `account`, `verification`, `twoFactor`, `rate_limit`, `einladung`; the OAuth provider's with `mcp`), the `protected…`/`admin…` helpers, the remote functions of accounts and Einladungen, the route loads, `createAuthClient()`, the test helpers.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies []:
|
|
12
|
+
- @vonall/mail@0.1.1
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SebädrHecht
|
|
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
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vonall/auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Accounts for a Web App Factory Product: better-auth with the signup hooks (signup modes `invite`, `open`, `admin`;
|
|
4
|
+
the first account is the admin), Einladungen, Konto, the three account mails, the `protected…`/`admin…` helpers for
|
|
5
|
+
remote functions, the remote functions of accounts and Einladungen, the auth routes' `load`s; Admin as an option.
|
|
6
|
+
The pages and the settings tabs follow in a later release.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
// src/lib/remote/schema/auth.sql.ts: the tables, re-exported so drizzle-kit sees them (the Product keeps the migrations)
|
|
10
|
+
import { authSchema } from "@vonall/auth/schema";
|
|
11
|
+
export const schema = authSchema({ admin: true, mcp: true });
|
|
12
|
+
export const { user, session, account, verification, twoFactor, rateLimit, einladung } = schema;
|
|
13
|
+
|
|
14
|
+
// src/lib/auth.ts: the wiring, once
|
|
15
|
+
import { createAuth } from "@vonall/auth/server";
|
|
16
|
+
export const { auth, handle: sessionHandle, apiHandler } = createAuth({
|
|
17
|
+
db, schema, secret: BETTER_AUTH_SECRET, origin: ORIGIN, appName: APP_NAME, locale: LOCALE, signupMode: SIGNUP_MODE,
|
|
18
|
+
mailer, admin: true, social: { github: { clientId, clientSecret } }, log: logger,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/hooks.server.ts sequence(securityHeaders, sessionHandle)
|
|
22
|
+
// src/routes/api/auth/[...auth] export const GET = apiHandler, POST = apiHandler
|
|
23
|
+
// src/routes/auth/login export { loginLoad as load } from "@vonall/auth/server"
|
|
24
|
+
// src/app.d.ts user: AuthUser | null; session: AuthSession | null (from "@vonall/auth/server")
|
|
25
|
+
// src/lib/auth-client.ts createAuthClient({ baseURL: PUBLIC_ORIGIN, admin: true }) (from "@vonall/auth/client")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Entry points: `@vonall/auth` (types, validation schemas, `isAdmin`, `mayInvite`, the fresh-session helpers, the
|
|
29
|
+
messages), `@vonall/auth/schema`, `@vonall/auth/server`, `@vonall/auth/client`, `@vonall/auth/remote` (the remote
|
|
30
|
+
functions a Product's components call), `@vonall/auth/testing` (`createUser`, `testAuth` for db tests).
|
|
31
|
+
|
|
32
|
+
Strings come in English and German: `configureAuthMessages({ locale, overrides })`. A Product's own user-owned tables
|
|
33
|
+
reference `user.id` with `onDelete: "cascade"`, so deleting a Konto takes everything with it. The MCP endpoint's
|
|
34
|
+
better-auth plugins and rate limit rule come from `@vonall/mcp` through `plugins` and `rateLimitRules`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAuthClient as createClient } from "better-auth/svelte";
|
|
2
|
+
type ClientPlugin = NonNullable<Parameters<typeof createClient>[0]>["plugins"] extends (infer P)[] | undefined ? P : never;
|
|
3
|
+
export type CreateAuthClientOptions = {
|
|
4
|
+
/** PUBLIC_ORIGIN. */
|
|
5
|
+
baseURL: string;
|
|
6
|
+
/** The Admin option (matches the server). */
|
|
7
|
+
admin?: boolean;
|
|
8
|
+
/** More client plugins, e.g. the MCP endpoint's (`@vonall/mcp/client`). */
|
|
9
|
+
plugins?: ClientPlugin[];
|
|
10
|
+
};
|
|
11
|
+
declare function build({ baseURL, admin, plugins }: CreateAuthClientOptions): import("better-auth/svelte").SvelteAuthClient<{
|
|
12
|
+
baseURL: string;
|
|
13
|
+
plugins: any[];
|
|
14
|
+
}>;
|
|
15
|
+
export type AuthClient = ReturnType<typeof build>;
|
|
16
|
+
export declare function createAuthClient(options: CreateAuthClientOptions): AuthClient;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The better-auth client for a Product's browser code; `src/lib/auth-client.ts` creates it once. */
|
|
2
|
+
import { adminClient, twoFactorClient } from "better-auth/client/plugins";
|
|
3
|
+
import { createAuthClient as createClient } from "better-auth/svelte";
|
|
4
|
+
import { ac, roles } from "../permissions.js";
|
|
5
|
+
function build({ baseURL, admin = false, plugins = [] }) {
|
|
6
|
+
return createClient({
|
|
7
|
+
baseURL: baseURL || "http://localhost:5173",
|
|
8
|
+
plugins: [
|
|
9
|
+
twoFactorClient(),
|
|
10
|
+
// The admin client's types are part of the client's type whether or not the option is on
|
|
11
|
+
...(admin ? [adminClient({ ac, roles })] : []),
|
|
12
|
+
...plugins,
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function createAuthClient(options) {
|
|
17
|
+
return build(options);
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { configureAuthMessages, m, type AuthMessages, type Locale } from "./messages.js";
|
|
2
|
+
export { ac, adminRole, roles, userRole } from "./permissions.js";
|
|
3
|
+
export { emailSchema, MIN_PASSWORD_LENGTH, nameSchema, passwordSchema, SIGNUP_MODES, SIGNUP_STATES, type AppUserClient, type EinladungInfo, type OffeneEinladung, type SessionUser, type SignupMode, type SignupState, type UserInsert, type UserSelect, } from "./types.js";
|
|
4
|
+
export { isSessionNotFresh, isStale, reauthPath, SESSION_NOT_FRESH } from "./util/fresh.js";
|
|
5
|
+
export { messageOf } from "./util/messageOf.js";
|
|
6
|
+
export { displayInitials, displayName, nameFromEmail } from "./util/names.js";
|
|
7
|
+
export { sameSitePath } from "./util/sameSite.js";
|
|
8
|
+
export { isAdmin, mayInvite, signupState } from "./util/signup.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// What both the browser and the server may import; the server side is `@vonall/auth/server`, the browser's client
|
|
2
|
+
// `@vonall/auth/client`, the remote functions `@vonall/auth/remote`, the tables `@vonall/auth/schema`.
|
|
3
|
+
export { configureAuthMessages, m } from "./messages.js";
|
|
4
|
+
export { ac, adminRole, roles, userRole } from "./permissions.js";
|
|
5
|
+
export { emailSchema, MIN_PASSWORD_LENGTH, nameSchema, passwordSchema, SIGNUP_MODES, SIGNUP_STATES, } from "./types.js";
|
|
6
|
+
export { isSessionNotFresh, isStale, reauthPath, SESSION_NOT_FRESH } from "./util/fresh.js";
|
|
7
|
+
export { messageOf } from "./util/messageOf.js";
|
|
8
|
+
export { displayInitials, displayName, nameFromEmail } from "./util/names.js";
|
|
9
|
+
export { sameSitePath } from "./util/sameSite.js";
|
|
10
|
+
export { isAdmin, mayInvite, signupState } from "./util/signup.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's strings, in English and German (Swiss spelling): what the server answers, what the mails say, and
|
|
3
|
+
* (from the UI release on) what the pages show. A Product calls configureAuthMessages once, from a module its root
|
|
4
|
+
* layout and its server wiring import, with its LOCALE and the few words it wants differently; code reads `m()`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const en: {
|
|
7
|
+
needsEinladung: string;
|
|
8
|
+
noMail: string;
|
|
9
|
+
onlyAdminsInvite: string;
|
|
10
|
+
einladungUnbekannt: string;
|
|
11
|
+
einladungAbgelaufen: string;
|
|
12
|
+
einladungAngenommen: string;
|
|
13
|
+
unauthorized: string;
|
|
14
|
+
forbidden: string;
|
|
15
|
+
ownRole: string;
|
|
16
|
+
ownAccount: string;
|
|
17
|
+
passwordsMismatch: string;
|
|
18
|
+
mailFailed: string;
|
|
19
|
+
invalidEmail: string;
|
|
20
|
+
passwordTooShort: (min: number) => string;
|
|
21
|
+
nameRequired: string;
|
|
22
|
+
nameTooLong: (max: number) => string;
|
|
23
|
+
hello: (name: string) => string;
|
|
24
|
+
resetSubject: (app: string) => string;
|
|
25
|
+
resetText: (name: string, app: string, link: string) => string;
|
|
26
|
+
resetButton: string;
|
|
27
|
+
resetBody: (app: string) => string;
|
|
28
|
+
resetNotYou: string;
|
|
29
|
+
verifySubject: (app: string) => string;
|
|
30
|
+
verifyText: (name: string, app: string, link: string) => string;
|
|
31
|
+
verifyButton: string;
|
|
32
|
+
verifyBody: (app: string) => string;
|
|
33
|
+
verifyNotYou: string;
|
|
34
|
+
einladungSubject: (inviter: string, app: string) => string;
|
|
35
|
+
einladungText: (inviter: string, app: string, link: string, validUntil: string) => string;
|
|
36
|
+
einladungTitle: (app: string) => string;
|
|
37
|
+
einladungButton: string;
|
|
38
|
+
einladungHello: string;
|
|
39
|
+
einladungBody: (inviter: string, app: string) => string;
|
|
40
|
+
einladungValid: (validUntil: string) => string;
|
|
41
|
+
};
|
|
42
|
+
export type AuthMessages = typeof en;
|
|
43
|
+
export declare const de: AuthMessages;
|
|
44
|
+
export type Locale = "en" | "de";
|
|
45
|
+
export declare function configureAuthMessages({ locale, overrides }: {
|
|
46
|
+
locale: Locale;
|
|
47
|
+
overrides?: Partial<AuthMessages>;
|
|
48
|
+
}): void;
|
|
49
|
+
/** The configured messages. */
|
|
50
|
+
export declare const m: () => AuthMessages;
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's strings, in English and German (Swiss spelling): what the server answers, what the mails say, and
|
|
3
|
+
* (from the UI release on) what the pages show. A Product calls configureAuthMessages once, from a module its root
|
|
4
|
+
* layout and its server wiring import, with its LOCALE and the few words it wants differently; code reads `m()`.
|
|
5
|
+
*/
|
|
6
|
+
export const en = {
|
|
7
|
+
// Signup
|
|
8
|
+
needsEinladung: "Signing up needs an invitation",
|
|
9
|
+
noMail: "Accounts can't be created at the moment",
|
|
10
|
+
onlyAdminsInvite: "Only admins can invite people",
|
|
11
|
+
// Einladung
|
|
12
|
+
einladungUnbekannt: "This invitation does not exist (any more)",
|
|
13
|
+
einladungAbgelaufen: "This invitation has expired",
|
|
14
|
+
einladungAngenommen: "This invitation has already been used",
|
|
15
|
+
// Remote functions
|
|
16
|
+
unauthorized: "Unauthorized",
|
|
17
|
+
forbidden: "Forbidden",
|
|
18
|
+
ownRole: "You can't change your own role",
|
|
19
|
+
ownAccount: "Delete your own account under Settings",
|
|
20
|
+
passwordsMismatch: "The passwords don't match",
|
|
21
|
+
mailFailed: "The email could not be sent",
|
|
22
|
+
// Validation
|
|
23
|
+
invalidEmail: "Please enter a valid email address",
|
|
24
|
+
passwordTooShort: (min) => `The password needs at least ${min} characters`,
|
|
25
|
+
nameRequired: "Name is required",
|
|
26
|
+
nameTooLong: (max) => `At most ${max} characters`,
|
|
27
|
+
// Mails: subjects, plain-text bodies (the footer carries the app's name) and the HTML templates' strings
|
|
28
|
+
hello: (name) => `Hello ${name || "there"},`,
|
|
29
|
+
resetSubject: (app) => `Reset your password - ${app}`,
|
|
30
|
+
resetText: (name, app, link) => `${en.hello(name)}\n\nYou asked for a new password for ${app}. Set it here:\n\n${link}\n\nIf that wasn't you, just ignore this email.\n`,
|
|
31
|
+
resetButton: "Reset your password",
|
|
32
|
+
resetBody: (app) => `You asked for a new password for ${app}. The button below lets you set it:`,
|
|
33
|
+
resetNotYou: "If that wasn't you, just ignore this email; your password stays as it is.",
|
|
34
|
+
verifySubject: (app) => `Confirm your email address - ${app}`,
|
|
35
|
+
verifyText: (name, app, link) => `${en.hello(name)}\n\nYou created an account at ${app}. Confirm that this address is yours:\n\n${link}\n\nIf that wasn't you, just ignore this email.\n`,
|
|
36
|
+
verifyButton: "Confirm your email address",
|
|
37
|
+
verifyBody: (app) => `You created an account at ${app}. Confirm that this address is yours to start using it:`,
|
|
38
|
+
verifyNotYou: "If that wasn't you, just ignore this email; without the confirmation the account can't be used.",
|
|
39
|
+
einladungSubject: (inviter, app) => `${inviter || "Someone"} invites you to ${app}`,
|
|
40
|
+
einladungText: (inviter, app, link, validUntil) => `Hello,\n\n${inviter || "Someone"} invites you to ${app}.\n\nAccept the invitation: ${link}\n\nThe link works once and is valid until ${validUntil}.\n`,
|
|
41
|
+
einladungTitle: (app) => `Invitation to ${app}`,
|
|
42
|
+
einladungButton: "Accept the invitation",
|
|
43
|
+
einladungHello: "Hello,",
|
|
44
|
+
einladungBody: (inviter, app) => `${inviter || "Someone"} invites you to ${app}.`,
|
|
45
|
+
einladungValid: (validUntil) => `The link works once and is valid until ${validUntil}.`,
|
|
46
|
+
};
|
|
47
|
+
export const de = {
|
|
48
|
+
needsEinladung: "Zum Registrieren braucht es eine Einladung",
|
|
49
|
+
noMail: "Im Moment können keine Konten erstellt werden",
|
|
50
|
+
onlyAdminsInvite: "Nur die Verwaltung kann Leute einladen",
|
|
51
|
+
einladungUnbekannt: "Diese Einladung gibt es nicht (mehr)",
|
|
52
|
+
einladungAbgelaufen: "Diese Einladung ist abgelaufen",
|
|
53
|
+
einladungAngenommen: "Diese Einladung wurde schon verwendet",
|
|
54
|
+
unauthorized: "Nicht angemeldet",
|
|
55
|
+
forbidden: "Nicht erlaubt",
|
|
56
|
+
ownRole: "Die eigene Rolle lässt sich nicht ändern",
|
|
57
|
+
ownAccount: "Das eigene Konto löschst du in den Einstellungen",
|
|
58
|
+
passwordsMismatch: "Die Passwörter stimmen nicht überein",
|
|
59
|
+
mailFailed: "Die E-Mail konnte nicht verschickt werden",
|
|
60
|
+
invalidEmail: "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
61
|
+
passwordTooShort: (min) => `Das Passwort braucht mindestens ${min} Zeichen`,
|
|
62
|
+
nameRequired: "Der Name fehlt",
|
|
63
|
+
nameTooLong: (max) => `Höchstens ${max} Zeichen`,
|
|
64
|
+
hello: (name) => (name ? `Hallo ${name}` : "Hallo"),
|
|
65
|
+
resetSubject: (app) => `Passwort zurücksetzen – ${app}`,
|
|
66
|
+
resetText: (name, app, link) => `${de.hello(name)}\n\nDu hast ein neues Passwort für ${app} angefordert. Hier legst du es fest:\n\n${link}\n\nWarst du das nicht, ignoriere diese E-Mail einfach.\n`,
|
|
67
|
+
resetButton: "Passwort zurücksetzen",
|
|
68
|
+
resetBody: (app) => `Du hast ein neues Passwort für ${app} angefordert. Mit dem Knopf unten legst du es fest:`,
|
|
69
|
+
resetNotYou: "Warst du das nicht, ignoriere diese E-Mail einfach; dein Passwort bleibt, wie es ist.",
|
|
70
|
+
verifySubject: (app) => `E-Mail-Adresse bestätigen – ${app}`,
|
|
71
|
+
verifyText: (name, app, link) => `${de.hello(name)}\n\nDu hast ein Konto bei ${app} erstellt. Bestätige, dass diese Adresse dir gehört:\n\n${link}\n\nWarst du das nicht, ignoriere diese E-Mail einfach.\n`,
|
|
72
|
+
verifyButton: "E-Mail-Adresse bestätigen",
|
|
73
|
+
verifyBody: (app) => `Du hast ein Konto bei ${app} erstellt. Bestätige, dass diese Adresse dir gehört, um loszulegen:`,
|
|
74
|
+
verifyNotYou: "Warst du das nicht, ignoriere diese E-Mail einfach; ohne Bestätigung lässt sich das Konto nicht nutzen.",
|
|
75
|
+
einladungSubject: (inviter, app) => `${inviter || "Jemand"} lädt dich zu ${app} ein`,
|
|
76
|
+
einladungText: (inviter, app, link, validUntil) => `Hallo\n\n${inviter || "Jemand"} lädt dich zu ${app} ein.\n\nEinladung annehmen: ${link}\n\nDer Link funktioniert einmal und ist bis ${validUntil} gültig.\n`,
|
|
77
|
+
einladungTitle: (app) => `Einladung zu ${app}`,
|
|
78
|
+
einladungButton: "Einladung annehmen",
|
|
79
|
+
einladungHello: "Hallo",
|
|
80
|
+
einladungBody: (inviter, app) => `${inviter || "Jemand"} lädt dich zu ${app} ein.`,
|
|
81
|
+
einladungValid: (validUntil) => `Der Link funktioniert einmal und ist bis ${validUntil} gültig.`,
|
|
82
|
+
};
|
|
83
|
+
let current = en;
|
|
84
|
+
export function configureAuthMessages({ locale, overrides }) {
|
|
85
|
+
current = { ...(locale === "de" ? de : en), ...overrides };
|
|
86
|
+
}
|
|
87
|
+
/** The configured messages. */
|
|
88
|
+
export const m = () => current;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const ac: {
|
|
2
|
+
newRole<const TRoleStatements extends import("better-auth/plugins").Statements>(statements: import("better-auth/plugins").RoleInput<{
|
|
3
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
4
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
5
|
+
}, TRoleStatements>): import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<TRoleStatements>, {
|
|
6
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
7
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
8
|
+
}>;
|
|
9
|
+
statements: {
|
|
10
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
11
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The app admin manages accounts but can't act as or take over a friend's account: no impersonation, no setting a
|
|
16
|
+
* password or an email, no creating accounts (signup only through an Einladung).
|
|
17
|
+
*/
|
|
18
|
+
export declare const adminRole: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
|
|
19
|
+
readonly user: ["list", "get", "set-role", "ban", "delete"];
|
|
20
|
+
readonly session: ["list", "revoke"];
|
|
21
|
+
}>, {
|
|
22
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
23
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
24
|
+
}>;
|
|
25
|
+
export declare const userRole: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
|
|
26
|
+
readonly user: [];
|
|
27
|
+
readonly session: [];
|
|
28
|
+
}>, {
|
|
29
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
30
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
31
|
+
}>;
|
|
32
|
+
/** The roles as better-auth's admin plugin and client take them. */
|
|
33
|
+
export declare const roles: {
|
|
34
|
+
admin: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
|
|
35
|
+
readonly user: ["list", "get", "set-role", "ban", "delete"];
|
|
36
|
+
readonly session: ["list", "revoke"];
|
|
37
|
+
}>, {
|
|
38
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
39
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
40
|
+
}>;
|
|
41
|
+
user: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
|
|
42
|
+
readonly user: [];
|
|
43
|
+
readonly session: [];
|
|
44
|
+
}>, {
|
|
45
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"];
|
|
46
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
47
|
+
}>;
|
|
48
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAccessControl } from "better-auth/plugins/access";
|
|
2
|
+
import { defaultStatements } from "better-auth/plugins/admin/access";
|
|
3
|
+
export const ac = createAccessControl(defaultStatements);
|
|
4
|
+
/**
|
|
5
|
+
* The app admin manages accounts but can't act as or take over a friend's account: no impersonation, no setting a
|
|
6
|
+
* password or an email, no creating accounts (signup only through an Einladung).
|
|
7
|
+
*/
|
|
8
|
+
export const adminRole = ac.newRole({
|
|
9
|
+
user: ["list", "get", "set-role", "ban", "delete"],
|
|
10
|
+
session: ["list", "revoke"],
|
|
11
|
+
});
|
|
12
|
+
export const userRole = ac.newRole({
|
|
13
|
+
user: [],
|
|
14
|
+
session: [],
|
|
15
|
+
});
|
|
16
|
+
/** The roles as better-auth's admin plugin and client take them. */
|
|
17
|
+
export const roles = { admin: adminRole, user: userRole };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The Admin option: every account. */
|
|
2
|
+
export declare const getAllUsers: import("$app/server").RemoteQueryFunction<void, {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
image: string | null;
|
|
7
|
+
emailVerified: boolean;
|
|
8
|
+
createdAt: Date;
|
|
9
|
+
updatedAt: Date;
|
|
10
|
+
twoFactorEnabled: boolean | null;
|
|
11
|
+
role: string;
|
|
12
|
+
banned: boolean;
|
|
13
|
+
banReason: string | null;
|
|
14
|
+
banExpires: Date | null;
|
|
15
|
+
}[], void>;
|
|
16
|
+
export declare const updateUserProfile: import("$app/server").RemoteForm<import("$app/server").RemoteFormInput, {
|
|
17
|
+
success: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const changePassword: import("$app/server").RemoteForm<import("$app/server").RemoteFormInput, {
|
|
20
|
+
success: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
export declare const setPassword: import("$app/server").RemoteForm<import("$app/server").RemoteFormInput, {
|
|
23
|
+
success: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const getSessions: import("$app/server").RemoteQueryFunction<void, import("better-auth").Prettify<{
|
|
26
|
+
id: string;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
userId: string;
|
|
30
|
+
expiresAt: Date;
|
|
31
|
+
token: string;
|
|
32
|
+
ipAddress?: string | null | undefined | undefined;
|
|
33
|
+
userAgent?: string | null | undefined | undefined;
|
|
34
|
+
impersonatedBy?: string | null | undefined;
|
|
35
|
+
}>[], void>;
|
|
36
|
+
export declare const revokeSessionById: import("$app/server").RemoteCommand<{
|
|
37
|
+
sessionId: string;
|
|
38
|
+
}, {
|
|
39
|
+
success: boolean;
|
|
40
|
+
isCurrentSession: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
export declare const revokeOthers: import("$app/server").RemoteCommand<void, {
|
|
43
|
+
success: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
export declare const getLinkedAccounts: import("$app/server").RemoteQueryFunction<void, {
|
|
46
|
+
id: string;
|
|
47
|
+
providerId: string;
|
|
48
|
+
updatedAt: Date;
|
|
49
|
+
}[], void>;
|
|
50
|
+
/** The Admin option. */
|
|
51
|
+
export declare const setUserRole: import("$app/server").RemoteCommand<{
|
|
52
|
+
userId: string;
|
|
53
|
+
role: "user" | "admin";
|
|
54
|
+
}, {
|
|
55
|
+
success: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
/** The Admin option: the admin deletes another account with all its data. */
|
|
58
|
+
export declare const deleteUser: import("$app/server").RemoteCommand<{
|
|
59
|
+
userId: string;
|
|
60
|
+
}, {
|
|
61
|
+
success: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/** Deletes the user's own account with all its data. */
|
|
64
|
+
export declare const deleteOwnAccount: import("$app/server").RemoteCommand<void, {
|
|
65
|
+
success: boolean;
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { error, redirect } from "@sveltejs/kit";
|
|
2
|
+
import { getRequestEvent } from "$app/server";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { m } from "../messages.js";
|
|
5
|
+
import { adminQuery, adminSchemaCommand, protectedCommand, protectedForm, protectedQuery, protectedSchemaCommand } from "../server/helpers.js";
|
|
6
|
+
import { registry } from "../server/registry.js";
|
|
7
|
+
import { UserRepo } from "../server/repositories/UserRepo.js";
|
|
8
|
+
import { KontoService } from "../server/service/konto.service.js";
|
|
9
|
+
import { emailSchema, nameSchema, passwordSchema } from "../types.js";
|
|
10
|
+
import { isSessionNotFresh, reauthPath } from "../util/fresh.js";
|
|
11
|
+
/** The Admin option: every account. */
|
|
12
|
+
export const getAllUsers = adminQuery(async () => {
|
|
13
|
+
return await UserRepo.getAllUsers();
|
|
14
|
+
});
|
|
15
|
+
export const updateUserProfile = protectedForm(z.object({ name: nameSchema, email: emailSchema }), async (data, _issue, user) => {
|
|
16
|
+
const { auth } = registry();
|
|
17
|
+
const event = getRequestEvent();
|
|
18
|
+
if (data.email !== user.email) {
|
|
19
|
+
await auth.api.changeEmail({ body: { newEmail: data.email }, headers: event.request.headers });
|
|
20
|
+
}
|
|
21
|
+
await auth.api.updateUser({ body: { name: data.name }, headers: event.request.headers });
|
|
22
|
+
return { success: true };
|
|
23
|
+
});
|
|
24
|
+
const passwordPair = { newPassword: passwordSchema, confirmPassword: z.string() };
|
|
25
|
+
const passwordsMatch = { error: () => m().passwordsMismatch, path: ["confirmPassword"] };
|
|
26
|
+
export const changePassword = protectedForm(z.object({ currentPassword: passwordSchema, ...passwordPair }).refine((d) => d.newPassword === d.confirmPassword, passwordsMatch), async (data) => {
|
|
27
|
+
const event = getRequestEvent();
|
|
28
|
+
await registry().auth.api.changePassword({
|
|
29
|
+
body: { currentPassword: data.currentPassword, newPassword: data.newPassword, revokeOtherSessions: true },
|
|
30
|
+
headers: event.request.headers,
|
|
31
|
+
});
|
|
32
|
+
return { success: true };
|
|
33
|
+
});
|
|
34
|
+
export const setPassword = protectedForm(z.object(passwordPair).refine((d) => d.newPassword === d.confirmPassword, passwordsMatch), async (data) => {
|
|
35
|
+
const event = getRequestEvent();
|
|
36
|
+
await registry().auth.api.setPassword({ body: { newPassword: data.newPassword }, headers: event.request.headers });
|
|
37
|
+
return { success: true };
|
|
38
|
+
});
|
|
39
|
+
export const getSessions = protectedQuery(async () => {
|
|
40
|
+
const event = getRequestEvent();
|
|
41
|
+
try {
|
|
42
|
+
return await registry().auth.api.listSessions({ headers: event.request.headers });
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
// Only a login from the last day may list the sessions; the sessions are in the settings
|
|
46
|
+
if (isSessionNotFresh(e))
|
|
47
|
+
redirect(303, reauthPath("/home/settings?tab=sessions"));
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
export const revokeSessionById = protectedSchemaCommand(z.object({ sessionId: z.string() }), async (data) => {
|
|
52
|
+
const event = getRequestEvent();
|
|
53
|
+
const currentSessionToken = event.locals.session?.token;
|
|
54
|
+
await registry().auth.api.revokeSession({ body: { token: data.sessionId }, headers: event.request.headers });
|
|
55
|
+
const isCurrentSession = data.sessionId === currentSessionToken;
|
|
56
|
+
if (!isCurrentSession)
|
|
57
|
+
await getSessions().refresh();
|
|
58
|
+
return { success: true, isCurrentSession };
|
|
59
|
+
});
|
|
60
|
+
export const revokeOthers = protectedCommand(async () => {
|
|
61
|
+
const event = getRequestEvent();
|
|
62
|
+
await registry().auth.api.revokeOtherSessions({ headers: event.request.headers });
|
|
63
|
+
await getSessions().refresh();
|
|
64
|
+
return { success: true };
|
|
65
|
+
});
|
|
66
|
+
export const getLinkedAccounts = protectedQuery(async (user) => {
|
|
67
|
+
return await UserRepo.getUserAccounts(user.id);
|
|
68
|
+
});
|
|
69
|
+
/** The Admin option. */
|
|
70
|
+
export const setUserRole = adminSchemaCommand(z.object({ userId: z.string(), role: z.enum(["admin", "user"]) }), async ({ userId, role }, admin) => {
|
|
71
|
+
if (userId === admin.id) {
|
|
72
|
+
throw error(400, { message: m().ownRole, id: "own_role", timestamp: new Date().toISOString() });
|
|
73
|
+
}
|
|
74
|
+
await UserRepo.setRole(userId, role);
|
|
75
|
+
await getAllUsers().refresh();
|
|
76
|
+
return { success: true };
|
|
77
|
+
});
|
|
78
|
+
/** The Admin option: the admin deletes another account with all its data. */
|
|
79
|
+
export const deleteUser = adminSchemaCommand(z.object({ userId: z.string() }), async ({ userId }, admin) => {
|
|
80
|
+
if (userId === admin.id) {
|
|
81
|
+
throw error(400, { message: m().ownAccount, id: "own_account", timestamp: new Date().toISOString() });
|
|
82
|
+
}
|
|
83
|
+
await KontoService.loeschen(userId);
|
|
84
|
+
await getAllUsers().refresh();
|
|
85
|
+
return { success: true };
|
|
86
|
+
});
|
|
87
|
+
/** Deletes the user's own account with all its data. */
|
|
88
|
+
export const deleteOwnAccount = protectedCommand(async (user) => {
|
|
89
|
+
await KontoService.loeschen(user.id);
|
|
90
|
+
return { success: true };
|
|
91
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The user's open Einladungen, and whether the server can send them by email. */
|
|
2
|
+
export declare const getOffeneEinladungen: import("$app/server").RemoteQueryFunction<void, {
|
|
3
|
+
einladungen: import("../types.js").OffeneEinladung[];
|
|
4
|
+
mail: boolean;
|
|
5
|
+
}, void>;
|
|
6
|
+
/** Creates an Einladung; the link is returned only here. With an email, it is also sent (if mail is set up). */
|
|
7
|
+
export declare const createEinladung: import("$app/server").RemoteCommand<{
|
|
8
|
+
email: string | null;
|
|
9
|
+
}, {
|
|
10
|
+
link: string;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
mailFehler: string | null;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const withdrawEinladung: import("$app/server").RemoteCommand<{
|
|
15
|
+
einladungId: string;
|
|
16
|
+
}, void>;
|
|
17
|
+
/** The Admin option: every open Einladung, and withdrawing any of them. */
|
|
18
|
+
export declare const getAlleEinladungen: import("$app/server").RemoteQueryFunction<void, {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string | null;
|
|
21
|
+
inviterName: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
}[], void>;
|
|
25
|
+
export declare const adminWithdrawEinladung: import("$app/server").RemoteCommand<{
|
|
26
|
+
einladungId: string;
|
|
27
|
+
}, void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { error } from "@sveltejs/kit";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { m } from "../messages.js";
|
|
4
|
+
import { adminQuery, adminSchemaCommand, protectedQuery, protectedSchemaCommand } from "../server/helpers.js";
|
|
5
|
+
import { registry } from "../server/registry.js";
|
|
6
|
+
import { EinladungService } from "../server/service/einladung.service.js";
|
|
7
|
+
import { sendEinladungEmail } from "../server/service/mail.js";
|
|
8
|
+
import { emailSchema } from "../types.js";
|
|
9
|
+
import { messageOf } from "../util/messageOf.js";
|
|
10
|
+
import { isAdmin, mayInvite } from "../util/signup.js";
|
|
11
|
+
function unwrap(result) {
|
|
12
|
+
if (result.isOk())
|
|
13
|
+
return result.value;
|
|
14
|
+
throw error(result.error.kind === "unbekannt" ? 404 : 400, { message: result.error.message, id: result.error.kind, timestamp: new Date().toISOString() });
|
|
15
|
+
}
|
|
16
|
+
/** The user's open Einladungen, and whether the server can send them by email. */
|
|
17
|
+
export const getOffeneEinladungen = protectedQuery(async (user) => {
|
|
18
|
+
return { einladungen: await EinladungService.offene(user.id), mail: registry().mailer.enabled };
|
|
19
|
+
});
|
|
20
|
+
/** Creates an Einladung; the link is returned only here. With an email, it is also sent (if mail is set up). */
|
|
21
|
+
export const createEinladung = protectedSchemaCommand(z.object({ email: emailSchema.nullable() }), async ({ email }, user) => {
|
|
22
|
+
const { origin, signupMode, admin, locale } = registry();
|
|
23
|
+
if (!mayInvite(signupMode, isAdmin(user))) {
|
|
24
|
+
throw error(403, { message: m().onlyAdminsInvite, id: "forbidden", timestamp: new Date().toISOString() });
|
|
25
|
+
}
|
|
26
|
+
const einladung = await EinladungService.erstellen(user.id, { email });
|
|
27
|
+
const link = `${origin}/einladung/${einladung.token}`;
|
|
28
|
+
let mailFehler = null;
|
|
29
|
+
if (email) {
|
|
30
|
+
try {
|
|
31
|
+
await sendEinladungEmail(user.name, email, { link, expiresAt: einladung.expiresAt }, locale);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
mailFehler = messageOf(e, m().mailFailed);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await getOffeneEinladungen().refresh();
|
|
38
|
+
if (admin && isAdmin(user))
|
|
39
|
+
await getAlleEinladungen().refresh();
|
|
40
|
+
return { link, expiresAt: einladung.expiresAt, mailFehler };
|
|
41
|
+
});
|
|
42
|
+
export const withdrawEinladung = protectedSchemaCommand(z.object({ einladungId: z.string() }), async ({ einladungId }, user) => {
|
|
43
|
+
unwrap(await EinladungService.zurueckziehen(user.id, einladungId));
|
|
44
|
+
await getOffeneEinladungen().refresh();
|
|
45
|
+
if (registry().admin && isAdmin(user))
|
|
46
|
+
await getAlleEinladungen().refresh();
|
|
47
|
+
});
|
|
48
|
+
/** The Admin option: every open Einladung, and withdrawing any of them. */
|
|
49
|
+
export const getAlleEinladungen = adminQuery(async () => {
|
|
50
|
+
return await EinladungService.alleOffenen();
|
|
51
|
+
});
|
|
52
|
+
export const adminWithdrawEinladung = adminSchemaCommand(z.object({ einladungId: z.string() }), async ({ einladungId }) => {
|
|
53
|
+
unwrap(await EinladungService.zurueckziehenAlsAdmin(einladungId));
|
|
54
|
+
await getAlleEinladungen().refresh();
|
|
55
|
+
await getOffeneEinladungen().refresh();
|
|
56
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { changePassword, deleteOwnAccount, deleteUser, getAllUsers, getLinkedAccounts, getSessions, revokeOthers, revokeSessionById, setPassword, setUserRole, updateUserProfile, } from "./app_user.remote.js";
|
|
2
|
+
export { adminWithdrawEinladung, createEinladung, getAlleEinladungen, getOffeneEinladungen, withdrawEinladung } from "./einladung.remote.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { changePassword, deleteOwnAccount, deleteUser, getAllUsers, getLinkedAccounts, getSessions, revokeOthers, revokeSessionById, setPassword, setUserRole, updateUserProfile, } from "./app_user.remote.js";
|
|
2
|
+
export { adminWithdrawEinladung, createEinladung, getAlleEinladungen, getOffeneEinladungen, withdrawEinladung } from "./einladung.remote.js";
|