- 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>
93 lines
2.8 KiB
HTML
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>
|