feat: switch login to hosted QueoAuth widget; tolerate username-only users

- index.html: load https://auth.queo.ru/widget.js
- auth.ts: openLogin() opens widget modal; useAuth() subscribes to widget
  onAuthChange so login in any tab updates the app instantly. Falls back
  to hosted login redirect if widget isn't loaded yet.
- App.tsx: render Landing page for unauthenticated state instead of
  hard redirect. Display username; add Logout button to topbar and
  Forbidden screen. Header uses username || email || sub.
- api.ts: throw ApiError(401) on 401 instead of redirecting — App.tsx
  re-fetches /api/me and shows the landing.
- @doc-manager/shared AuthPayload: email is optional now, username and
  displayName accepted. Backend /api/me returns username.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-05-30 16:13:20 +03:00
parent 5e57fdfd11
commit 0c6deed98d
7 changed files with 161 additions and 41 deletions
+4 -1
View File
@@ -7,12 +7,15 @@ export type PermissionRole = z.infer<typeof PermissionRole>;
export const AuthPayload = z.object({
sub: z.string().uuid(),
email: z.string().email(),
username: z.string().optional(),
email: z.string().email().optional(),
displayName: z.string().optional(),
groups: z.array(z.string()).default([]),
permissions: z.record(PermissionRole).default({}),
isSuperuser: z.boolean().default(false),
iat: z.number().optional(),
exp: z.number().optional(),
jti: z.string().optional(),
iss: z.literal('https://auth.queo.ru').optional(),
aud: z.union([z.literal('queo.ru'), z.array(z.string())]).optional(),
});