sh3-core 0.5.4 → 0.5.5
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.
Potentially problematic release.
This version of sh3-core might be problematic. Click here for more details.
- package/dist/Shell.svelte +4 -1
- package/dist/admin/AuthSettingsView.svelte +105 -0
- package/dist/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/admin/SystemView.svelte +73 -0
- package/dist/admin/SystemView.svelte.d.ts +3 -0
- package/dist/admin/UsersView.svelte +189 -0
- package/dist/admin/UsersView.svelte.d.ts +3 -0
- package/dist/admin/adminApp.d.ts +7 -0
- package/dist/admin/adminApp.js +24 -0
- package/dist/admin/adminShard.svelte.d.ts +4 -0
- package/dist/admin/adminShard.svelte.js +52 -0
- package/dist/api.d.ts +2 -1
- package/dist/api.js +1 -1
- package/dist/auth/GuestBanner.svelte +144 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +213 -0
- package/dist/auth/SignInWall.svelte.d.ts +8 -0
- package/dist/auth/auth.svelte.d.ts +42 -31
- package/dist/auth/auth.svelte.js +106 -89
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.js +70 -16
- package/dist/createShell.d.ts +2 -2
- package/dist/createShell.js +78 -33
- package/dist/host-entry.d.ts +2 -1
- package/dist/host-entry.js +2 -2
- package/dist/host.d.ts +0 -2
- package/dist/host.js +11 -25
- package/dist/shell-shard/ShellHome.svelte +29 -115
- package/package.json +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SignInWall — standalone sign-in screen shown before shell boots.
|
|
4
|
+
* Mounted directly to the target element by createShell().
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { login, register } from './index';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
serverUrl: string;
|
|
11
|
+
selfRegistration: boolean;
|
|
12
|
+
onSuccess: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { serverUrl, selfRegistration, onSuccess }: Props = $props();
|
|
16
|
+
|
|
17
|
+
let mode = $state<'login' | 'register'>('login');
|
|
18
|
+
let username = $state('');
|
|
19
|
+
let password = $state('');
|
|
20
|
+
let displayName = $state('');
|
|
21
|
+
let error = $state<string | null>(null);
|
|
22
|
+
let loading = $state(false);
|
|
23
|
+
|
|
24
|
+
async function handleLogin() {
|
|
25
|
+
if (!username.trim() || !password.trim() || loading) return;
|
|
26
|
+
loading = true;
|
|
27
|
+
error = null;
|
|
28
|
+
const result = await login(username.trim(), password.trim());
|
|
29
|
+
loading = false;
|
|
30
|
+
if (result.ok) {
|
|
31
|
+
onSuccess();
|
|
32
|
+
} else {
|
|
33
|
+
error = result.error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function handleRegister() {
|
|
38
|
+
if (!username.trim() || !password.trim() || loading) return;
|
|
39
|
+
loading = true;
|
|
40
|
+
error = null;
|
|
41
|
+
const result = await register(
|
|
42
|
+
username.trim(),
|
|
43
|
+
password.trim(),
|
|
44
|
+
displayName.trim() || undefined,
|
|
45
|
+
);
|
|
46
|
+
loading = false;
|
|
47
|
+
if (result.ok) {
|
|
48
|
+
onSuccess();
|
|
49
|
+
} else {
|
|
50
|
+
error = result.error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function switchMode(m: 'login' | 'register') {
|
|
55
|
+
mode = m;
|
|
56
|
+
error = null;
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div class="signin-wall">
|
|
61
|
+
<div class="signin-card">
|
|
62
|
+
<h1 class="signin-brand">SH3</h1>
|
|
63
|
+
|
|
64
|
+
{#if mode === 'login'}
|
|
65
|
+
<form class="signin-form" onsubmit={(e) => { e.preventDefault(); handleLogin(); }}>
|
|
66
|
+
<input
|
|
67
|
+
class="signin-input"
|
|
68
|
+
type="text"
|
|
69
|
+
placeholder="Username"
|
|
70
|
+
bind:value={username}
|
|
71
|
+
disabled={loading}
|
|
72
|
+
autocomplete="username"
|
|
73
|
+
/>
|
|
74
|
+
<input
|
|
75
|
+
class="signin-input"
|
|
76
|
+
type="password"
|
|
77
|
+
placeholder="Password"
|
|
78
|
+
bind:value={password}
|
|
79
|
+
disabled={loading}
|
|
80
|
+
autocomplete="current-password"
|
|
81
|
+
/>
|
|
82
|
+
<button type="submit" class="signin-btn" disabled={loading || !username.trim() || !password.trim()}>
|
|
83
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
84
|
+
</button>
|
|
85
|
+
</form>
|
|
86
|
+
{#if selfRegistration}
|
|
87
|
+
<button type="button" class="signin-link" onclick={() => switchMode('register')}>
|
|
88
|
+
Create an account
|
|
89
|
+
</button>
|
|
90
|
+
{/if}
|
|
91
|
+
{:else}
|
|
92
|
+
<form class="signin-form" onsubmit={(e) => { e.preventDefault(); handleRegister(); }}>
|
|
93
|
+
<input
|
|
94
|
+
class="signin-input"
|
|
95
|
+
type="text"
|
|
96
|
+
placeholder="Username"
|
|
97
|
+
bind:value={username}
|
|
98
|
+
disabled={loading}
|
|
99
|
+
autocomplete="username"
|
|
100
|
+
/>
|
|
101
|
+
<input
|
|
102
|
+
class="signin-input"
|
|
103
|
+
type="text"
|
|
104
|
+
placeholder="Display name (optional)"
|
|
105
|
+
bind:value={displayName}
|
|
106
|
+
disabled={loading}
|
|
107
|
+
/>
|
|
108
|
+
<input
|
|
109
|
+
class="signin-input"
|
|
110
|
+
type="password"
|
|
111
|
+
placeholder="Password"
|
|
112
|
+
bind:value={password}
|
|
113
|
+
disabled={loading}
|
|
114
|
+
autocomplete="new-password"
|
|
115
|
+
/>
|
|
116
|
+
<button type="submit" class="signin-btn" disabled={loading || !username.trim() || !password.trim()}>
|
|
117
|
+
{loading ? 'Creating...' : 'Create account'}
|
|
118
|
+
</button>
|
|
119
|
+
</form>
|
|
120
|
+
<button type="button" class="signin-link" onclick={() => switchMode('login')}>
|
|
121
|
+
Back to sign in
|
|
122
|
+
</button>
|
|
123
|
+
{/if}
|
|
124
|
+
|
|
125
|
+
{#if error}
|
|
126
|
+
<div class="signin-error">{error}</div>
|
|
127
|
+
{/if}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<style>
|
|
132
|
+
.signin-wall {
|
|
133
|
+
position: absolute;
|
|
134
|
+
inset: 0;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
background: var(--shell-grad-bg, var(--shell-bg, #1a1a2e));
|
|
139
|
+
color: var(--shell-fg, #e0e0e0);
|
|
140
|
+
font-family: system-ui, sans-serif;
|
|
141
|
+
}
|
|
142
|
+
.signin-card {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 16px;
|
|
147
|
+
padding: 48px 40px;
|
|
148
|
+
background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #252540));
|
|
149
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
150
|
+
border-radius: var(--shell-radius-lg, 12px);
|
|
151
|
+
min-width: 320px;
|
|
152
|
+
}
|
|
153
|
+
.signin-brand {
|
|
154
|
+
margin: 0 0 8px;
|
|
155
|
+
font-size: 42px;
|
|
156
|
+
color: var(--shell-accent, #7c7cf0);
|
|
157
|
+
letter-spacing: 2px;
|
|
158
|
+
}
|
|
159
|
+
.signin-form {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
gap: 12px;
|
|
163
|
+
width: 100%;
|
|
164
|
+
}
|
|
165
|
+
.signin-input {
|
|
166
|
+
padding: 10px 14px;
|
|
167
|
+
background: var(--shell-bg, #1a1a2e);
|
|
168
|
+
color: var(--shell-fg, #e0e0e0);
|
|
169
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
170
|
+
border-radius: var(--shell-radius, 6px);
|
|
171
|
+
font-size: 14px;
|
|
172
|
+
}
|
|
173
|
+
.signin-input::placeholder {
|
|
174
|
+
color: var(--shell-fg-muted, #888);
|
|
175
|
+
}
|
|
176
|
+
.signin-btn {
|
|
177
|
+
padding: 10px 16px;
|
|
178
|
+
background: var(--shell-accent, #7c7cf0);
|
|
179
|
+
color: var(--shell-bg, #1a1a2e);
|
|
180
|
+
border: none;
|
|
181
|
+
border-radius: var(--shell-radius, 6px);
|
|
182
|
+
font-weight: 600;
|
|
183
|
+
font-size: 14px;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
}
|
|
186
|
+
.signin-btn:disabled {
|
|
187
|
+
opacity: 0.6;
|
|
188
|
+
cursor: not-allowed;
|
|
189
|
+
}
|
|
190
|
+
.signin-btn:hover:not(:disabled) {
|
|
191
|
+
filter: brightness(1.1);
|
|
192
|
+
}
|
|
193
|
+
.signin-link {
|
|
194
|
+
background: none;
|
|
195
|
+
border: none;
|
|
196
|
+
color: var(--shell-accent, #7c7cf0);
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
font-size: 13px;
|
|
199
|
+
padding: 0;
|
|
200
|
+
}
|
|
201
|
+
.signin-link:hover {
|
|
202
|
+
text-decoration: underline;
|
|
203
|
+
}
|
|
204
|
+
.signin-error {
|
|
205
|
+
padding: 8px 12px;
|
|
206
|
+
font-size: 13px;
|
|
207
|
+
color: var(--shell-error, #d32f2f);
|
|
208
|
+
background: color-mix(in srgb, var(--shell-error, #d32f2f) 10%, transparent);
|
|
209
|
+
border-radius: var(--shell-radius, 6px);
|
|
210
|
+
width: 100%;
|
|
211
|
+
text-align: center;
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
@@ -1,50 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-side
|
|
2
|
+
* Client-side auth — session-based identity for SH3.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The boot flow (createShell) calls initFromBoot() with the server's
|
|
5
|
+
* boot config. After that, login/logout call the server and update
|
|
6
|
+
* the reactive state. isAdmin/isGuest/isAuthenticated are reactive
|
|
7
|
+
* getters consumed by shell components.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* remains usable without admin access when the server is slow or offline.
|
|
10
|
-
*
|
|
11
|
-
* OS analogy: sudo / elevated permissions, not web login.
|
|
12
|
-
*
|
|
13
|
-
* .svelte.ts because it uses $state for reactive admin status.
|
|
9
|
+
* .svelte.ts because it uses $state for reactive auth status.
|
|
14
10
|
*/
|
|
11
|
+
import type { AuthUser, AuthSession, BootConfig } from './types';
|
|
15
12
|
/**
|
|
16
|
-
* Initialize auth
|
|
17
|
-
*
|
|
18
|
-
* If a key is stored from a previous session, verifies it against the
|
|
19
|
-
* server with a 3-second timeout. If verification fails (key revoked,
|
|
20
|
-
* server down, timeout), the shell boots without admin access — the
|
|
21
|
-
* stored key is cleared only on explicit rejection (401), not on
|
|
22
|
-
* network failure (so a temporary outage doesn't force re-entry).
|
|
23
|
-
*
|
|
24
|
-
* @param url - Server base URL ('' for same-origin).
|
|
13
|
+
* Initialize auth from boot config. Called once by createShell()
|
|
14
|
+
* after fetching /api/boot.
|
|
25
15
|
*/
|
|
26
|
-
export declare function
|
|
16
|
+
export declare function initFromBoot(url: string, config: BootConfig): void;
|
|
27
17
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
18
|
+
* Log in with username + password. On success, updates reactive state.
|
|
19
|
+
* Returns { ok: true } or { ok: false, error: string }.
|
|
30
20
|
*/
|
|
31
|
-
export declare function
|
|
21
|
+
export declare function login(username: string, password: string): Promise<{
|
|
22
|
+
ok: true;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
32
27
|
/**
|
|
33
|
-
*
|
|
28
|
+
* Register a new account (when self-registration is enabled).
|
|
29
|
+
* On success, auto-logs in and updates reactive state.
|
|
34
30
|
*/
|
|
35
|
-
export declare function
|
|
31
|
+
export declare function register(username: string, password: string, displayName?: string): Promise<{
|
|
32
|
+
ok: true;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
error: string;
|
|
36
|
+
}>;
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
+
* Log out — clear session on server and client.
|
|
38
39
|
*/
|
|
39
|
-
export declare function
|
|
40
|
+
export declare function logout(): Promise<void>;
|
|
40
41
|
/**
|
|
41
42
|
* Mark this session as local-owner — auto-elevate to admin without
|
|
42
|
-
*
|
|
43
|
-
* where the user owns the machine. Idempotent.
|
|
43
|
+
* server verification. Called by the host in Tauri / dev environments.
|
|
44
44
|
*/
|
|
45
45
|
export declare function setLocalOwner(): void;
|
|
46
|
+
/** Reactive — true when the user has admin role. */
|
|
47
|
+
export declare function isAdmin(): boolean;
|
|
48
|
+
/** Reactive — true when the user has a valid session. */
|
|
49
|
+
export declare function isAuthenticated(): boolean;
|
|
50
|
+
/** Reactive — true when browsing without a session. */
|
|
51
|
+
export declare function isGuest(): boolean;
|
|
52
|
+
/** Get the current user (reactive). */
|
|
53
|
+
export declare function getUser(): AuthUser | null;
|
|
54
|
+
/** Get the current session (reactive). */
|
|
55
|
+
export declare function getSession(): AuthSession | null;
|
|
46
56
|
/**
|
|
47
|
-
* Build an Authorization header value for authenticated fetch calls
|
|
48
|
-
*
|
|
57
|
+
* Build an Authorization header value for authenticated fetch calls
|
|
58
|
+
* that need explicit headers (e.g. non-cookie contexts).
|
|
59
|
+
* Returns null if not authenticated.
|
|
49
60
|
*/
|
|
50
61
|
export declare function getAuthHeader(): string | null;
|
package/dist/auth/auth.svelte.js
CHANGED
|
@@ -1,127 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-side
|
|
2
|
+
* Client-side auth — session-based identity for SH3.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The boot flow (createShell) calls initFromBoot() with the server's
|
|
5
|
+
* boot config. After that, login/logout call the server and update
|
|
6
|
+
* the reactive state. isAdmin/isGuest/isAuthenticated are reactive
|
|
7
|
+
* getters consumed by shell components.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* remains usable without admin access when the server is slow or offline.
|
|
10
|
-
*
|
|
11
|
-
* OS analogy: sudo / elevated permissions, not web login.
|
|
12
|
-
*
|
|
13
|
-
* .svelte.ts because it uses $state for reactive admin status.
|
|
9
|
+
* .svelte.ts because it uses $state for reactive auth status.
|
|
14
10
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
let admin = $state(false);
|
|
21
|
-
/** Server base URL, set once during initAuth. */
|
|
11
|
+
/** Reactive auth state. */
|
|
12
|
+
let currentUser = $state(null);
|
|
13
|
+
let currentSession = $state(null);
|
|
14
|
+
let guest = $state(false);
|
|
15
|
+
/** Server base URL, set during boot. */
|
|
22
16
|
let serverUrl = '';
|
|
23
17
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* Initialize auth from boot config. Called once by createShell()
|
|
19
|
+
* after fetching /api/boot.
|
|
26
20
|
*/
|
|
27
|
-
|
|
21
|
+
export function initFromBoot(url, config) {
|
|
22
|
+
serverUrl = url;
|
|
23
|
+
currentUser = config.user;
|
|
24
|
+
currentSession = config.session;
|
|
25
|
+
guest = !config.session && !config.user;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Log in with username + password. On success, updates reactive state.
|
|
29
|
+
* Returns { ok: true } or { ok: false, error: string }.
|
|
30
|
+
*/
|
|
31
|
+
export async function login(username, password) {
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
33
|
+
const res = await fetch(`${serverUrl}/api/auth/login`, {
|
|
30
34
|
method: 'POST',
|
|
31
|
-
headers: {
|
|
32
|
-
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
credentials: 'include',
|
|
37
|
+
body: JSON.stringify({ username, password }),
|
|
33
38
|
});
|
|
34
|
-
if (!
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.json().catch(() => ({}));
|
|
41
|
+
return { ok: false, error: body.error || 'Login failed' };
|
|
42
|
+
}
|
|
43
|
+
const body = await res.json();
|
|
44
|
+
currentUser = body.user;
|
|
45
|
+
currentSession = body.session;
|
|
46
|
+
guest = false;
|
|
47
|
+
return { ok: true };
|
|
38
48
|
}
|
|
39
49
|
catch (_a) {
|
|
40
|
-
return false;
|
|
50
|
+
return { ok: false, error: 'Network error' };
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* If a key is stored from a previous session, verifies it against the
|
|
47
|
-
* server with a 3-second timeout. If verification fails (key revoked,
|
|
48
|
-
* server down, timeout), the shell boots without admin access — the
|
|
49
|
-
* stored key is cleared only on explicit rejection (401), not on
|
|
50
|
-
* network failure (so a temporary outage doesn't force re-entry).
|
|
51
|
-
*
|
|
52
|
-
* @param url - Server base URL ('' for same-origin).
|
|
54
|
+
* Register a new account (when self-registration is enabled).
|
|
55
|
+
* On success, auto-logs in and updates reactive state.
|
|
53
56
|
*/
|
|
54
|
-
export async function
|
|
55
|
-
serverUrl = url;
|
|
56
|
-
const stored = state.user.apiKey;
|
|
57
|
-
if (!stored)
|
|
58
|
-
return;
|
|
59
|
-
const controller = new AbortController();
|
|
60
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
57
|
+
export async function register(username, password, displayName) {
|
|
61
58
|
try {
|
|
62
|
-
const
|
|
59
|
+
const res = await fetch(`${serverUrl}/api/auth/register`, {
|
|
63
60
|
method: 'POST',
|
|
64
|
-
headers: {
|
|
65
|
-
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
credentials: 'include',
|
|
63
|
+
body: JSON.stringify({ username, password, displayName }),
|
|
66
64
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (body.valid === true) {
|
|
71
|
-
admin = true;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Server explicitly rejected the key — clear it.
|
|
76
|
-
if (response.status === 401 || response.status === 403) {
|
|
77
|
-
state.user.apiKey = null;
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const body = await res.json().catch(() => ({}));
|
|
67
|
+
return { ok: false, error: body.error || 'Registration failed' };
|
|
78
68
|
}
|
|
79
|
-
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
currentUser = body.user;
|
|
71
|
+
currentSession = body.session;
|
|
72
|
+
guest = false;
|
|
73
|
+
return { ok: true };
|
|
80
74
|
}
|
|
81
75
|
catch (_a) {
|
|
82
|
-
|
|
83
|
-
// Network error or timeout: keep the key, boot unelevated.
|
|
84
|
-
// User can re-elevate manually once connectivity returns.
|
|
76
|
+
return { ok: false, error: 'Network error' };
|
|
85
77
|
}
|
|
86
78
|
}
|
|
87
79
|
/**
|
|
88
|
-
*
|
|
89
|
-
* before storing. Returns true on success, false if the key is invalid.
|
|
80
|
+
* Log out — clear session on server and client.
|
|
90
81
|
*/
|
|
91
|
-
export async function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
export async function logout() {
|
|
83
|
+
try {
|
|
84
|
+
await fetch(`${serverUrl}/api/auth/logout`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
credentials: 'include',
|
|
87
|
+
});
|
|
96
88
|
}
|
|
97
|
-
|
|
89
|
+
catch (_a) {
|
|
90
|
+
// Best effort
|
|
91
|
+
}
|
|
92
|
+
currentUser = null;
|
|
93
|
+
currentSession = null;
|
|
94
|
+
guest = true;
|
|
98
95
|
}
|
|
99
96
|
/**
|
|
100
|
-
*
|
|
97
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
98
|
+
* server verification. Called by the host in Tauri / dev environments.
|
|
101
99
|
*/
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
export function setLocalOwner() {
|
|
101
|
+
currentUser = {
|
|
102
|
+
id: 'local',
|
|
103
|
+
username: 'local',
|
|
104
|
+
displayName: 'Local Owner',
|
|
105
|
+
role: 'admin',
|
|
106
|
+
createdAt: '',
|
|
107
|
+
updatedAt: '',
|
|
108
|
+
};
|
|
109
|
+
currentSession = {
|
|
110
|
+
token: 'local',
|
|
111
|
+
userId: 'local',
|
|
112
|
+
role: 'admin',
|
|
113
|
+
expiresAt: Infinity,
|
|
114
|
+
};
|
|
115
|
+
guest = false;
|
|
105
116
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Reactive getter — true when the user has elevated to admin mode.
|
|
108
|
-
*/
|
|
117
|
+
/** Reactive — true when the user has admin role. */
|
|
109
118
|
export function isAdmin() {
|
|
110
|
-
return admin;
|
|
119
|
+
return (currentSession === null || currentSession === void 0 ? void 0 : currentSession.role) === 'admin';
|
|
111
120
|
}
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*/
|
|
117
|
-
export function
|
|
118
|
-
|
|
121
|
+
/** Reactive — true when the user has a valid session. */
|
|
122
|
+
export function isAuthenticated() {
|
|
123
|
+
return currentSession !== null;
|
|
124
|
+
}
|
|
125
|
+
/** Reactive — true when browsing without a session. */
|
|
126
|
+
export function isGuest() {
|
|
127
|
+
return guest;
|
|
128
|
+
}
|
|
129
|
+
/** Get the current user (reactive). */
|
|
130
|
+
export function getUser() {
|
|
131
|
+
return currentUser;
|
|
132
|
+
}
|
|
133
|
+
/** Get the current session (reactive). */
|
|
134
|
+
export function getSession() {
|
|
135
|
+
return currentSession;
|
|
119
136
|
}
|
|
120
137
|
/**
|
|
121
|
-
* Build an Authorization header value for authenticated fetch calls
|
|
122
|
-
*
|
|
138
|
+
* Build an Authorization header value for authenticated fetch calls
|
|
139
|
+
* that need explicit headers (e.g. non-cookie contexts).
|
|
140
|
+
* Returns null if not authenticated.
|
|
123
141
|
*/
|
|
124
142
|
export function getAuthHeader() {
|
|
125
|
-
|
|
126
|
-
return key ? `Bearer ${key}` : null;
|
|
143
|
+
return currentSession ? `Bearer ${currentSession.token}` : null;
|
|
127
144
|
}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { initFromBoot, login, logout, register, isAdmin, isAuthenticated, isGuest, getUser, getSession, getAuthHeader, setLocalOwner, } from './auth.svelte';
|
|
2
|
+
export type { AuthUser, AuthSession, BootConfig, GlobalSettings } from './types';
|
package/dist/auth/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { initFromBoot, login, logout, register, isAdmin, isAuthenticated, isGuest, getUser, getSession, getAuthHeader, setLocalOwner, } from './auth.svelte';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared auth types — used by both client and server.
|
|
3
|
+
* Kept in sh3-core so the server can import them at build time
|
|
4
|
+
* and the client uses them directly.
|
|
5
|
+
*/
|
|
6
|
+
/** Public user shape (never includes passwordHash). */
|
|
7
|
+
export interface AuthUser {
|
|
8
|
+
id: string;
|
|
9
|
+
username: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
role: 'admin' | 'user';
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
/** Session shape returned to the client. */
|
|
16
|
+
export interface AuthSession {
|
|
17
|
+
token: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
role: 'admin' | 'user';
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
}
|
|
22
|
+
/** Response from GET /api/boot. */
|
|
23
|
+
export interface BootConfig {
|
|
24
|
+
auth: {
|
|
25
|
+
required: boolean;
|
|
26
|
+
guestAllowed: boolean;
|
|
27
|
+
selfRegistration: boolean;
|
|
28
|
+
};
|
|
29
|
+
user: AuthUser | null;
|
|
30
|
+
session: AuthSession | null;
|
|
31
|
+
tenantId: string;
|
|
32
|
+
}
|
|
33
|
+
/** Global settings shape. */
|
|
34
|
+
export interface GlobalSettings {
|
|
35
|
+
auth: {
|
|
36
|
+
required: boolean;
|
|
37
|
+
guestAllowed: boolean;
|
|
38
|
+
sessionTTL: number;
|
|
39
|
+
selfRegistration: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|