@merijnslegers/admin 0.1.0 → 0.2.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/README.md +8 -1
- package/package.json +1 -1
- package/src/rules.d.ts +7 -0
- package/src/rules.js +14 -0
- package/src/server.d.ts +3 -1
- package/src/server.js +2 -3
package/README.md
CHANGED
|
@@ -52,7 +52,12 @@ export const admin = createAdmin({
|
|
|
52
52
|
serviceRoleKey: SUPABASE_SERVICE_ROLE_KEY,
|
|
53
53
|
allowList: ADMIN_EMAILS,
|
|
54
54
|
// Optional: a project in Dutch names its screens in Dutch.
|
|
55
|
-
paths: { login: '/admin/inloggen', password: '/admin/wachtwoord' }
|
|
55
|
+
paths: { login: '/admin/inloggen', password: '/admin/wachtwoord' },
|
|
56
|
+
// Optional: the hook's 401 and 403 in the project's own words.
|
|
57
|
+
responses: {
|
|
58
|
+
unauthorized: 'U bent niet ingelogd.',
|
|
59
|
+
forbidden: 'Dit verzoek kwam niet van onze eigen site.'
|
|
60
|
+
}
|
|
56
61
|
});
|
|
57
62
|
```
|
|
58
63
|
|
|
@@ -74,6 +79,8 @@ For everything under the admin path it:
|
|
|
74
79
|
- answers a request that can change something with a 403 when it does not come
|
|
75
80
|
from the site's own origin, even when SvelteKit's own check is off for a
|
|
76
81
|
webhook;
|
|
82
|
+
- gives that 401 or 403 the text from `responses` as plain text, or an empty
|
|
83
|
+
body without it, so the package chooses no words of its own;
|
|
77
84
|
- puts `ADMIN_HEADERS` on every response: no framing, `noindex`, `no-store`,
|
|
78
85
|
a referrer that stays on the site.
|
|
79
86
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merijnslegers/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The admin login of the software factory: Supabase Auth with an allow list, the access rules and headers, and image resizing in the browser.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/rules.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ export type AdminDecision =
|
|
|
13
13
|
| { kind: 'unauthorized' }
|
|
14
14
|
| { kind: 'forbidden' };
|
|
15
15
|
|
|
16
|
+
/** The project's own words for the hook's 401 and 403. A missing one is an empty body. */
|
|
17
|
+
export interface AdminResponses {
|
|
18
|
+
unauthorized?: string;
|
|
19
|
+
forbidden?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export interface SettingNames {
|
|
17
23
|
supabaseUrl: string;
|
|
18
24
|
anonKey: string;
|
|
@@ -45,6 +51,7 @@ export function adminDecision(request: {
|
|
|
45
51
|
signedIn: boolean;
|
|
46
52
|
paths: AdminPaths;
|
|
47
53
|
}): AdminDecision;
|
|
54
|
+
export function refusal(kind: 'unauthorized' | 'forbidden', responses?: AdminResponses): Response;
|
|
48
55
|
export function missingSettings(
|
|
49
56
|
settings: {
|
|
50
57
|
supabaseUrl: string;
|
package/src/rules.js
CHANGED
|
@@ -153,6 +153,20 @@ export function adminDecision({ url, method, contentType, origin, signedIn, path
|
|
|
153
153
|
return { kind: 'unauthorized' };
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The answer to an `unauthorized` (401) or `forbidden` (403) decision. The
|
|
158
|
+
* package chooses no words (F-0011): the body is the project's own text from
|
|
159
|
+
* `responses`, or empty when it passes none. `ADMIN_HEADERS` are on it too.
|
|
160
|
+
*/
|
|
161
|
+
export function refusal(kind, responses = {}) {
|
|
162
|
+
const status = kind === 'forbidden' ? 403 : 401;
|
|
163
|
+
const text = (kind === 'forbidden' ? responses.forbidden : responses.unauthorized) ?? '';
|
|
164
|
+
const headers = new Headers(ADMIN_HEADERS);
|
|
165
|
+
if (!text) return new Response(null, { status, headers });
|
|
166
|
+
headers.set('Content-Type', 'text/plain; charset=utf-8');
|
|
167
|
+
return new Response(text, { status, headers });
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
/**
|
|
157
171
|
* The settings logging in needs that are missing, by the names the project
|
|
158
172
|
* gave them. Empty when everything is there. Without them nobody gets in
|
package/src/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Cookies } from '@sveltejs/kit';
|
|
2
2
|
import type { Handle } from '@sveltejs/kit/hooks';
|
|
3
|
-
import type { AdminPaths, SettingNames } from './rules.js';
|
|
3
|
+
import type { AdminPaths, AdminResponses, SettingNames } from './rules.js';
|
|
4
4
|
|
|
5
5
|
export interface AdminOptions {
|
|
6
6
|
/** Read at runtime in the project's src/env.ts (F-0005). An empty string means missing. */
|
|
@@ -13,6 +13,8 @@ export interface AdminOptions {
|
|
|
13
13
|
paths?: Partial<AdminPaths>;
|
|
14
14
|
/** The names `missingSettings()` reports, if the project's variables are named differently. */
|
|
15
15
|
settingNames?: Partial<SettingNames>;
|
|
16
|
+
/** The body of the hook's 401 and 403, in the project's own words. Without it the body is empty. */
|
|
17
|
+
responses?: AdminResponses;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface Admin {
|
package/src/server.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
parseAllowList,
|
|
25
25
|
passwordOutcome,
|
|
26
26
|
passwordProblem,
|
|
27
|
+
refusal,
|
|
27
28
|
signInOutcome
|
|
28
29
|
} from './rules.js';
|
|
29
30
|
|
|
@@ -106,9 +107,7 @@ export function createAdmin(options) {
|
|
|
106
107
|
const response =
|
|
107
108
|
decision.kind === 'pass'
|
|
108
109
|
? await resolve(event)
|
|
109
|
-
:
|
|
110
|
-
status: decision.kind === 'forbidden' ? 403 : 401
|
|
111
|
-
});
|
|
110
|
+
: refusal(decision.kind, options.responses);
|
|
112
111
|
for (const [name, value] of Object.entries(ADMIN_HEADERS)) response.headers.set(name, value);
|
|
113
112
|
return response;
|
|
114
113
|
}
|