feat(profile): self-service name/email/password + desktop installers in CI
Self-service profile: any signed-in user edits their OWN fullName/email and changes their OWN password (proving the current one), without any user:* permission. New routes PUT /api/auth/profile + /api/auth/password act only on req.user.sub (cannot touch username/role), CSRF-guarded; SPA screen at /profile reachable from the header username chip. email added to the session view + SessionUser. 7 tests (routes/profile.test.ts); 148 server tests green. Desktop in CI: new .gitea/workflows/build-desktop.yml builds .deb + .AppImage on every push to dev/main and uploads them as unsigned workflow artifacts (per-commit test build). Signed/versioned release stays on release.yml (tag v*). Wiki: local-jwt-auth (self-service routes), desktop-shell-tauri (two-workflow CI split), log entry. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -75,6 +75,8 @@ export interface SessionUser {
|
||||
theme: Theme;
|
||||
/** Optional display name (profile metadata); null if unset. */
|
||||
fullName: string | null;
|
||||
/** Optional contact email (profile metadata); null if unset. */
|
||||
email: string | null;
|
||||
}
|
||||
|
||||
/** Does this session grant the permission? Central authz check for the SPA. */
|
||||
@@ -103,6 +105,29 @@ export function setThemePref(theme: Theme): Promise<{ theme: Theme }> {
|
||||
return apiFetch("/api/auth/theme", { method: "PUT", body: JSON.stringify({ theme }) });
|
||||
}
|
||||
|
||||
/** Edit MY own profile (display name / email). Returns the refreshed session.
|
||||
* Self-service — touches only the signed-in user; no `user:*` permission needed. */
|
||||
export function updateMyProfile(patch: {
|
||||
fullName?: string | null;
|
||||
email?: string | null;
|
||||
}): Promise<SessionUser> {
|
||||
return apiFetch<SessionUser>("/api/auth/profile", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
}
|
||||
|
||||
/** Change MY own password — proves the current one first (server enforces). */
|
||||
export function changeMyPassword(
|
||||
currentPassword: string,
|
||||
newPassword: string,
|
||||
): Promise<{ ok: boolean }> {
|
||||
return apiFetch("/api/auth/password", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ currentPassword, newPassword }),
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns the current user, or null if not authenticated. */
|
||||
export async function fetchMe(): Promise<SessionUser | null> {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user