forked from alterminal/alterminal
重組前檢查點:根目錄 main package
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
{{/* 註冊新應用程式頁(僅 admin,獨立於管理列表頁)。以 layout.html(側邊
|
||||
導覽欄版面)為根模板組合渲染:本檔僅定義區塊,不應單獨解析執行;
|
||||
並引用 secretpanel.html 的一次性成果面板。導覽覆寫同管理列表頁
|
||||
(「應用程式管理」標記 aria-current)。註冊成功時於 POST 回應直接
|
||||
顯示成果(明文 client secret 僅此一次,故不採 PRG);驗證失敗重繪
|
||||
時保留輸入(含核取方塊)。 */}}
|
||||
{{define "title"}}註冊新應用程式 - alterminal{{end}}
|
||||
|
||||
{{define "navitems"}}
|
||||
<li>
|
||||
<a href="/login"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/>
|
||||
</svg>
|
||||
帳號資訊
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/keys"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"/>
|
||||
</svg>
|
||||
金鑰管理
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/applications" aria-current="page"
|
||||
class="flex items-center gap-3 rounded-lg bg-brand/10 px-3 py-2 text-sm font-medium text-brand dark:bg-brand/25 dark:text-blue-200">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/>
|
||||
</svg>
|
||||
應用程式管理
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="rounded-xl bg-white p-8 shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="mb-1 text-xl font-semibold">註冊新應用程式</h1>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">接入 OIDC 的應用程式(Relying Party)註冊</p>
|
||||
</div>
|
||||
<a href="/admin/applications"
|
||||
class="flex items-center gap-2 rounded-lg border border-neutral-300 px-4 py-2.5 text-sm font-semibold hover:bg-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-700">
|
||||
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/>
|
||||
</svg>
|
||||
返回列表
|
||||
</a>
|
||||
</div>
|
||||
{{if .Error}}<p class="mb-4 rounded-lg bg-red-500/10 px-3 py-2.5 text-sm text-red-600 dark:text-red-400" role="alert">{{.Error}}</p>{{end}}
|
||||
{{if .Secret}}{{template "secretpanel" .Secret}}{{end}}
|
||||
|
||||
<form method="post" action="/admin/applications/new" class="space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRF}}">
|
||||
<div>
|
||||
<label for="app-name" class="mb-1 block text-sm font-medium">名稱</label>
|
||||
<input id="app-name" name="name" type="text" required maxlength="255" value="{{.Form.Name}}"
|
||||
class="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm dark:border-neutral-600 dark:bg-neutral-900">
|
||||
</div>
|
||||
<div>
|
||||
<label for="app-type" class="mb-1 block text-sm font-medium">類型</label>
|
||||
<select id="app-type" name="type"
|
||||
class="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm dark:border-neutral-600 dark:bg-neutral-900">
|
||||
<option value="confidential" {{if eq .Form.Type "confidential"}}selected{{end}}>機密式 confidential(後端應用,發配 client secret)</option>
|
||||
<option value="public" {{if eq .Form.Type "public"}}selected{{end}}>公開式 public(SPA/行動應用,無 secret,須用 PKCE)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="app-redirect-uris" class="mb-1 block text-sm font-medium">Redirect URI(每行一個)</label>
|
||||
<textarea id="app-redirect-uris" name="redirect_uris" rows="3"
|
||||
class="w-full rounded-lg border border-neutral-300 px-3 py-2 font-mono text-xs dark:border-neutral-600 dark:bg-neutral-900">{{.Form.RedirectURIs}}</textarea>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">須為絕對 URI;http 僅允許 localhost/127.0.0.1/::1,其餘請使用 https;原生應用可用自訂 scheme(如 com.example.app:/cb)。</p>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend class="mb-1 text-sm font-medium">允許的 grant type</legend>
|
||||
<div class="space-y-1.5 text-sm">
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="grant_types" value="authorization_code" class="size-4" {{if .Form.GrantAuthCode}}checked{{end}}> authorization_code(授權碼流程)</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="grant_types" value="refresh_token" class="size-4" {{if .Form.GrantRefresh}}checked{{end}}> refresh_token(Refresh Token)</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="grant_types" value="client_credentials" class="size-4" {{if .Form.GrantClientCred}}checked{{end}}> client_credentials(機器對機器,僅機密式)</label>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">全不勾選時預設 authorization_code。</p>
|
||||
</fieldset>
|
||||
<div>
|
||||
<label for="app-scope" class="mb-1 block text-sm font-medium">Scope</label>
|
||||
<input id="app-scope" name="scope" type="text" value="{{.Form.Scope}}" placeholder="openid profile email"
|
||||
class="w-full rounded-lg border border-neutral-300 px-3 py-2 font-mono text-xs dark:border-neutral-600 dark:bg-neutral-900">
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">空格分隔,可用:openid、profile、email、offline_access;留空使用預設 openid profile email(offline_access 須勾選 refresh_token)。</p>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="rounded-lg bg-brand px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-strong">註冊應用程式</button>
|
||||
</form>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,111 @@
|
||||
{{/* 應用程式管理頁(僅 admin)。以 layout.html(側邊導覽欄版面)為根模板
|
||||
組合渲染:本檔僅定義區塊,不應單獨解析執行;並引用 secretpanel.html
|
||||
的一次性成果面板。導覽覆寫為「帳號資訊+金鑰管理+應用程式管理」
|
||||
(後者標記 aria-current),側欄頁尾沿用版面預設。註冊表單獨立於
|
||||
/admin/applications/new(本頁按鈕進入);輪替成功時於 POST 回應直接
|
||||
顯示明文 client secret(僅此一次,故不採 PRG)。 */}}
|
||||
{{define "title"}}應用程式管理 - alterminal{{end}}
|
||||
|
||||
{{define "navitems"}}
|
||||
<li>
|
||||
<a href="/login"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/>
|
||||
</svg>
|
||||
帳號資訊
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/keys"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"/>
|
||||
</svg>
|
||||
金鑰管理
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/applications" aria-current="page"
|
||||
class="flex items-center gap-3 rounded-lg bg-brand/10 px-3 py-2 text-sm font-medium text-brand dark:bg-brand/25 dark:text-blue-200">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/>
|
||||
</svg>
|
||||
應用程式管理
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="rounded-xl bg-white p-8 shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="mb-1 text-xl font-semibold">應用程式管理</h1>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">接入 OIDC 的應用程式(Relying Party)註冊</p>
|
||||
</div>
|
||||
<a href="/admin/applications/new"
|
||||
class="flex items-center gap-2 rounded-lg bg-brand px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-strong">
|
||||
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
|
||||
</svg>
|
||||
註冊新應用程式
|
||||
</a>
|
||||
</div>
|
||||
{{if .Error}}<p class="mb-4 rounded-lg bg-red-500/10 px-3 py-2.5 text-sm text-red-600 dark:text-red-400" role="alert">{{.Error}}</p>{{end}}
|
||||
{{if .Secret}}{{template "secretpanel" .Secret}}{{end}}
|
||||
|
||||
{{if .Apps}}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-neutral-200 dark:border-neutral-700">
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">名稱</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">client_id</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">類型</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">redirect URI</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">grant type</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">scope</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">建立時間</th>
|
||||
<th scope="col" class="px-3 py-2"><span class="sr-only">操作</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-neutral-100 dark:divide-neutral-700/60">
|
||||
{{range .Apps}}
|
||||
<tr>
|
||||
<td class="px-3 py-3 font-medium">{{.Name}}</td>
|
||||
<td class="px-3 py-3 font-mono text-xs break-all">{{.ClientID}}</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap">
|
||||
{{if .Confidential}}
|
||||
<span class="rounded-full bg-blue-500/10 px-2.5 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-400">機密式</span>
|
||||
{{else}}
|
||||
<span class="rounded-full bg-violet-500/10 px-2.5 py-0.5 text-xs font-medium text-violet-700 dark:text-violet-400">公開式</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="px-3 py-3 font-mono text-xs break-all whitespace-pre-line">{{.RedirectURIs}}</td>
|
||||
<td class="px-3 py-3 text-xs">{{.GrantTypes}}</td>
|
||||
<td class="px-3 py-3 font-mono text-xs break-all">{{.Scope}}</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap text-neutral-500 dark:text-neutral-400">{{.CreatedAt}}</td>
|
||||
<td class="px-3 py-3">
|
||||
{{if .Confidential}}
|
||||
<form method="post" action="/admin/applications/{{.ID}}/secret" class="mb-1">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRF}}">
|
||||
<button type="submit" title="產生新 client secret(舊的立即失效)"
|
||||
class="rounded-lg border border-neutral-300 px-3 py-1.5 text-xs font-semibold hover:bg-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-700">輪替 secret</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form method="post" action="/admin/applications/{{.ID}}/delete">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRF}}">
|
||||
<button type="submit" title="刪除後此應用程式無法再登入"
|
||||
class="rounded-lg border border-red-300 px-3 py-1.5 text-xs font-semibold text-red-600 hover:bg-red-50 dark:border-red-500/60 dark:text-red-400 dark:hover:bg-red-500/10">刪除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="py-6 text-sm text-neutral-500 dark:text-neutral-400">尚無應用程式,點選上方「註冊新應用程式」建立第一個。</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,99 @@
|
||||
{{/* 金鑰管理頁(僅 admin)。以 layout.html(側邊導覽欄版面)為根模板組合
|
||||
渲染:本檔僅定義區塊,不應單獨解析執行。導覽覆寫為「帳號資訊+金鑰
|
||||
管理(aria-current)+應用程式管理」,側欄頁尾沿用版面預設(使用者
|
||||
資訊與登出表單)。 */}}
|
||||
{{define "title"}}金鑰管理 - alterminal{{end}}
|
||||
|
||||
{{define "navitems"}}
|
||||
<li>
|
||||
<a href="/login"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/>
|
||||
</svg>
|
||||
帳號資訊
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/keys" aria-current="page"
|
||||
class="flex items-center gap-3 rounded-lg bg-brand/10 px-3 py-2 text-sm font-medium text-brand dark:bg-brand/25 dark:text-blue-200">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"/>
|
||||
</svg>
|
||||
金鑰管理
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/applications"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/>
|
||||
</svg>
|
||||
應用程式管理
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="rounded-xl bg-white p-8 shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<div class="mb-6 flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="mb-1 text-xl font-semibold">金鑰管理</h1>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">JWT 簽章金鑰(RSA-2048 · RS256)</p>
|
||||
</div>
|
||||
<form method="post" action="/admin/keys">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRF}}">
|
||||
<button type="submit"
|
||||
class="rounded-lg bg-brand px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-strong">產生新金鑰</button>
|
||||
</form>
|
||||
</div>
|
||||
{{if .Error}}<p class="mb-4 rounded-lg bg-red-500/10 px-3 py-2.5 text-sm text-red-600 dark:text-red-400" role="alert">{{.Error}}</p>{{end}}
|
||||
{{if not .ActiveCount}}<p class="mb-4 rounded-lg bg-amber-500/10 px-3 py-2.5 text-sm text-amber-700 dark:text-amber-400" role="alert">目前沒有使用中的金鑰,將無法簽發 JWT,請立即產生新金鑰。</p>{{end}}
|
||||
{{if .Keys}}
|
||||
<p class="mb-3 text-sm text-neutral-500 dark:text-neutral-400">使用中 {{.ActiveCount}} 把 / 共 {{len .Keys}} 把</p>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-neutral-200 dark:border-neutral-700">
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">kid</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">演算法</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">建立時間</th>
|
||||
<th scope="col" class="px-3 py-2 text-xs font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400">狀態</th>
|
||||
<th scope="col" class="px-3 py-2"><span class="sr-only">操作</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-neutral-100 dark:divide-neutral-700/60">
|
||||
{{range .Keys}}
|
||||
<tr>
|
||||
<td class="px-3 py-3 font-mono text-xs break-all">{{.Kid}}</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap">{{.Algorithm}}</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap text-neutral-500 dark:text-neutral-400">{{.CreatedAt}}</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap">
|
||||
{{if .Active}}
|
||||
<span class="rounded-full bg-emerald-500/10 px-2.5 py-0.5 text-xs font-medium text-emerald-700 dark:text-emerald-400">使用中</span>
|
||||
{{else}}
|
||||
<span class="rounded-full bg-neutral-500/10 px-2.5 py-0.5 text-xs font-medium text-neutral-600 dark:text-neutral-400">已退休</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="px-3 py-3 whitespace-nowrap">
|
||||
{{if .Active}}{{if .LastActive}}
|
||||
<span class="text-xs text-neutral-400 dark:text-neutral-500" title="最後一把使用中金鑰,無法退休">唯一使用中金鑰</span>
|
||||
{{else}}
|
||||
<form method="post" action="/admin/keys/{{.ID}}/retire">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRF}}">
|
||||
<button type="submit"
|
||||
class="rounded-lg border border-neutral-300 px-3 py-1.5 text-xs font-semibold hover:bg-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-700">退休</button>
|
||||
</form>
|
||||
{{end}}{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-neutral-500 dark:text-neutral-400">輪替方式:先「產生新金鑰」並以新金鑰簽發,舊金鑰確認無人使用後再「退休」(退休後仍發佈於 JWKS 一段時間供驗證)。</p>
|
||||
{{else}}
|
||||
<p class="py-6 text-sm text-neutral-500 dark:text-neutral-400">尚無簽章金鑰,點上方「產生新金鑰」建立第一把。</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>{{block "title" .}}alterminal{{end}}</title>
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
</head>
|
||||
{{/*
|
||||
側邊導覽欄版面(app shell):桌面版(md+)側欄固定展開,主內容以
|
||||
md:pl-72 偏移;手機版側欄預設移出畫面外,以隱藏 checkbox(peer)搭配
|
||||
peer-checked: 變體開合——CSP 停用 JavaScript,故開合必須是純 CSS。
|
||||
頁面模板需定義 "content",可另定義 "title"、"navitems"、"sidebarfooter"
|
||||
(後三者未定義時使用此處的預設)。
|
||||
*/}}
|
||||
<body class="min-h-screen bg-neutral-100 font-sans text-neutral-900 antialiased dark:bg-neutral-900 dark:text-neutral-100">
|
||||
<div class="min-h-screen">
|
||||
<input type="checkbox" id="sidebar-toggle" class="peer sr-only" aria-label="切換側邊導覽列">
|
||||
<label for="sidebar-toggle" title="開啟導覽列"
|
||||
class="fixed left-4 top-4 z-50 flex size-11 cursor-pointer items-center justify-center rounded-lg border border-neutral-300 bg-white text-neutral-600 shadow-sm md:hidden peer-checked:hidden peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-brand dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-300">
|
||||
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
|
||||
</svg>
|
||||
</label>
|
||||
<label for="sidebar-toggle" aria-hidden="true"
|
||||
class="fixed inset-0 z-30 hidden cursor-pointer bg-neutral-900/40 peer-checked:block md:hidden!"></label>
|
||||
<aside aria-label="側邊導覽列"
|
||||
class="fixed inset-y-0 left-0 z-40 flex w-72 -translate-x-full flex-col border-r border-neutral-200 bg-white transition-transform duration-200 ease-in-out peer-checked:translate-x-0 md:translate-x-0 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<div class="flex items-center gap-3 border-b border-neutral-200 px-6 py-5 dark:border-neutral-700">
|
||||
<span class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-brand text-white">
|
||||
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block text-base font-semibold leading-tight">alterminal</span>
|
||||
<span class="block text-xs text-neutral-500 dark:text-neutral-400">單一登入服務</span>
|
||||
</span>
|
||||
</div>
|
||||
<nav aria-label="主要導覽" class="flex-1 overflow-y-auto px-3 py-4">
|
||||
<ul class="space-y-1">
|
||||
{{block "navitems" .}}
|
||||
<li>
|
||||
<a href="/" aria-current="page"
|
||||
class="flex items-center gap-3 rounded-lg bg-brand/10 px-3 py-2 text-sm font-medium text-brand dark:bg-brand/25 dark:text-blue-200">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/>
|
||||
</svg>
|
||||
帳號資訊
|
||||
</a>
|
||||
</li>
|
||||
{{if .IsAdmin}}
|
||||
<li>
|
||||
<a href="/admin/keys"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"/>
|
||||
</svg>
|
||||
金鑰管理
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/applications"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700/60">
|
||||
<svg class="size-5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/>
|
||||
</svg>
|
||||
應用程式管理
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="border-t border-neutral-200 px-4 py-4 dark:border-neutral-700">
|
||||
{{/* 預設頁尾:使用者資訊與登出表單(頁面資料需含 Username/Email/CSRF),
|
||||
未登入脈絡的頁面不應使用本版面。 */}}
|
||||
{{block "sidebarfooter" .}}
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium">{{.Username}}</p>
|
||||
<p class="truncate text-xs text-neutral-500 dark:text-neutral-400">{{.Email}}</p>
|
||||
</div>
|
||||
<form method="post" action="/logout" class="mt-3">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRF}}">
|
||||
<button type="submit"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg border border-neutral-300 py-2 text-sm font-semibold hover:bg-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-700">
|
||||
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0L18 12m0 0 2.25-2.25M18 12H9"/>
|
||||
</svg>
|
||||
登出
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
</aside>
|
||||
<div class="flex min-h-screen flex-col md:pl-72">
|
||||
<main class="mx-auto w-full max-w-3xl flex-1 p-4 md:p-10">
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
{{/* 已登入狀態頁。以 layout.html(側邊導覽欄版面)為根模板組合渲染:
|
||||
本檔僅定義區塊,不應單獨解析執行。導覽與側欄頁尾沿用版面預設
|
||||
(帳號資訊標記 aria-current;admin 另顯示金鑰管理連結;
|
||||
頁尾為使用者資訊與登出表單)。 */}}
|
||||
{{define "title"}}帳號資訊 - alterminal{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="rounded-xl bg-white p-8 shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<h1 class="mb-1 text-xl font-semibold">帳號資訊</h1>
|
||||
<p class="mb-6 text-sm text-neutral-500 dark:text-neutral-400">已登入:單一登入服務</p>
|
||||
{{if .Error}}<p class="mb-2 rounded-lg bg-red-500/10 px-3 py-2.5 text-sm text-red-600 dark:text-red-400" role="alert">{{.Error}}</p>{{end}}
|
||||
<dl class="m-0">
|
||||
<dt class="text-sm text-neutral-500 dark:text-neutral-400">帳號</dt>
|
||||
<dd class="mt-0.5 mb-3.5 text-[15px] break-all">{{.Username}}</dd>
|
||||
<dt class="text-sm text-neutral-500 dark:text-neutral-400">Email</dt>
|
||||
<dd class="mt-0.5 mb-3.5 text-[15px] break-all">{{.Email}}</dd>
|
||||
<dt class="text-sm text-neutral-500 dark:text-neutral-400">Session 到期</dt>
|
||||
<dd class="mt-0.5 mb-3.5 text-[15px] break-all">{{.ExpiresAt}}</dd>
|
||||
</dl>
|
||||
<p class="mt-4 text-sm text-neutral-500 dark:text-neutral-400">授權流程(/authorize)完成後,登入將自動導回應用程式。</p>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>登入 - alterminal</title>
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
</head>
|
||||
<body class="flex min-h-screen items-center justify-center bg-neutral-100 font-sans text-neutral-900 antialiased dark:bg-neutral-900 dark:text-neutral-100">
|
||||
<main class="m-4 w-full max-w-88 rounded-xl bg-white p-8 shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<h1 class="mb-1 text-xl font-semibold">登入 alterminal</h1>
|
||||
<p class="mb-6 text-sm text-neutral-500 dark:text-neutral-400">單一登入服務</p>
|
||||
{{if .Error}}<p class="mb-2 rounded-lg bg-red-500/10 px-3 py-2.5 text-sm text-red-600 dark:text-red-400" role="alert">{{.Error}}</p>{{end}}
|
||||
<form method="post" action="/login">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRF}}">
|
||||
<label for="username" class="mb-1 mt-4 block text-sm">帳號</label>
|
||||
<input type="text" id="username" name="username" value="{{.Username}}" autocomplete="username" autofocus required
|
||||
class="w-full rounded-lg border border-neutral-300 bg-transparent px-3 py-2.5 text-base focus:border-transparent focus:outline-2 focus:outline-offset-1 focus:outline-brand dark:border-neutral-600">
|
||||
<label for="password" class="mb-1 mt-4 block text-sm">密碼</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password" required
|
||||
class="w-full rounded-lg border border-neutral-300 bg-transparent px-3 py-2.5 text-base focus:border-transparent focus:outline-2 focus:outline-offset-1 focus:outline-brand dark:border-neutral-600">
|
||||
<button type="submit" class="mt-6 w-full rounded-lg bg-brand py-2.5 text-base font-semibold text-white hover:bg-brand-strong">登入</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>找不到頁面 - alterminal</title>
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
</head>
|
||||
{{/*
|
||||
404 頁採獨立版面(同登入頁):訪客可能未登入,無法提供側邊導覽欄
|
||||
版面所需的 Session 資料。CSP 停用 JavaScript,無法用 history.back(),
|
||||
僅提供回到 /login 的連結(未登入顯示登入表單、已登入顯示帳號資訊)。
|
||||
*/}}
|
||||
<body class="flex min-h-screen items-center justify-center bg-neutral-100 font-sans text-neutral-900 antialiased dark:bg-neutral-900 dark:text-neutral-100">
|
||||
<main class="m-4 w-full max-w-88 rounded-xl bg-white p-8 text-center shadow-lg dark:bg-neutral-800 dark:shadow-black/40">
|
||||
<p class="text-5xl font-bold tracking-tight text-brand">404</p>
|
||||
<h1 class="mb-1 mt-3 text-xl font-semibold">找不到頁面</h1>
|
||||
<p class="mb-6 text-sm text-neutral-500 dark:text-neutral-400">要求的頁面不存在,可能已被移動或網址有誤。</p>
|
||||
<a href="/login" class="inline-block w-full rounded-lg bg-brand px-4 py-2.5 text-base font-semibold text-white hover:bg-brand-strong">回到登入頁</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
{{/* 一次性成果面板(共用片段):註冊應用程式與輪替 client secret 成功時,
|
||||
於 POST 回應直接渲染——資料庫僅存雜湊,明文無法重現,故不採 PRG。
|
||||
僅定義 "secretpanel" 區塊供頁面模板以 {{template "secretpanel" .Secret}}
|
||||
引用,不應單獨解析執行。機密式顯示明文 client_secret 與保存警告;
|
||||
公開式無 secret,改提示以 PKCE 驗證授權請求。 */}}
|
||||
{{define "secretpanel"}}
|
||||
<div class="mb-6 rounded-lg border border-emerald-500/40 bg-emerald-500/10 p-4" role="status">
|
||||
{{if .Rotated}}
|
||||
<h2 class="mb-2 text-sm font-semibold text-emerald-700 dark:text-emerald-400">{{.Name}}:client secret 已輪替</h2>
|
||||
{{else if .Public}}
|
||||
<h2 class="mb-2 text-sm font-semibold text-emerald-700 dark:text-emerald-400">{{.Name}} 已註冊(公開式 Client)</h2>
|
||||
{{else}}
|
||||
<h2 class="mb-2 text-sm font-semibold text-emerald-700 dark:text-emerald-400">{{.Name}} 已註冊:client secret 已發配</h2>
|
||||
{{end}}
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<dt class="font-medium text-neutral-700 dark:text-neutral-300">client_id</dt>
|
||||
<dd class="font-mono text-xs break-all">{{.ClientID}}</dd>
|
||||
</div>
|
||||
{{if .Secret}}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<dt class="font-medium text-neutral-700 dark:text-neutral-300">client_secret</dt>
|
||||
<dd class="font-mono text-xs break-all">{{.Secret}}</dd>
|
||||
</div>
|
||||
{{end}}
|
||||
</dl>
|
||||
{{if .Secret}}
|
||||
<p class="mt-3 text-sm font-medium text-red-600 dark:text-red-400">此 client secret 只顯示這一次,關閉或重新整理頁面後將無法再查看,請立即交付給應用程式管理者妥善保存。</p>
|
||||
{{else}}
|
||||
<p class="mt-3 text-sm text-neutral-600 dark:text-neutral-300">公開式 Client 不持有 client secret,授權請求須以 PKCE(code_challenge)驗證。</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user