crimata-os/ui/index.html
Andrew Gundersen 44f3192b78 Add multi-user auth with sessions and login UI
- auth: POST /auth validates PAM, returns session token
- auth: GET /me validates Bearer token, returns username
- auth: POST /logout destroys session
- auth: POST /users creates Linux user via useradd+chpasswd
- session.c: in-memory token store (1024 slots, pthread mutex, /dev/urandom tokens)
- user.c: fork/exec useradd + chpasswd with username validation
- ui: login overlay shown until authenticated (x-cloak)
- ui: token persisted in sessionStorage, validated on page load
- ui: username displayed in dock after login
- ui: apps only load after successful auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:33:07 -05:00

93 lines
2.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Crimata</title>
<link rel="stylesheet" href="styles/canvas.css" />
<link rel="stylesheet" href="styles/window.css" />
<link rel="stylesheet" href="styles/dock.css" />
<link rel="stylesheet" href="styles/cursor.css" />
<link rel="stylesheet" href="styles/login.css" />
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body x-data="os" @keydown.window="handleKeydown">
<!-- Login overlay (shown until authenticated) -->
<div id="login-overlay" x-show="!authed" x-cloak>
<div class="login-card">
<h1>Crimata</h1>
<input
type="text"
x-model="loginUsername"
placeholder="Username"
@keydown.enter="$refs.loginPassword.focus()"
autocomplete="username"
/>
<input
type="password"
x-model="loginPassword"
placeholder="Password"
@keydown.enter="submitLogin"
x-ref="loginPassword"
autocomplete="current-password"
/>
<button @click="submitLogin">Sign in</button>
<div class="login-error" x-text="loginError"></div>
</div>
</div>
<!-- Infinite canvas -->
<div id="canvas"
@mousedown="startPan"
@mousemove="pan"
@mouseup="endPan"
@mouseleave="endPan"
:style="`transform: translate(${offset.x}px, ${offset.y}px)`"
>
<!-- Windows render here -->
<template x-for="win in windows" :key="win.id">
<div class="window"
:style="`left:${win.x}px; top:${win.y}px; width:${win.w}px; height:${win.h}px; z-index:${win.z}`"
@mousedown.stop="focusWindow(win)"
>
<div class="window-titlebar"
@mousedown.stop="startDrag($event, win)"
>
<span class="window-title" x-text="win.title"></span>
<button class="window-close" @click.stop="closeWindow(win)"></button>
</div>
<div class="window-content">
<iframe :src="win.url" frameborder="0"></iframe>
</div>
</div>
</template>
</div>
<!-- Cursor input overlay -->
<div id="cursor-input" x-show="inputMode" @click.stop>
<input
id="cursor-input-field"
type="text"
x-model="cursorQuery"
@keydown.enter="submitQuery"
@keydown.escape="exitInputMode"
placeholder="Type a command..."
x-ref="cursorInput"
/>
</div>
<!-- Dock -->
<div id="dock">
<template x-for="app in installedApps" :key="app.id">
<div class="dock-icon" @click="openApp(app)" :title="app.name">
<span x-text="app.icon"></span>
<span class="dock-dot" x-show="app.running"></span>
</div>
</template>
<div id="dock-user" x-show="currentUser" x-text="currentUser"></div>
</div>
<script src="js/os.js"></script>
</body>
</html>