From 8f7538039abbcefa0762556ee958690f69d86aeb Mon Sep 17 00:00:00 2001 From: Sor4chi <80559385+sor4chi@users.noreply.github.com> Date: Sat, 9 Nov 2024 13:09:12 +0900 Subject: [PATCH] fix: enhance oauth/authorization UI --- webapp/api/_templates/button.tsx | 34 +++++++++ webapp/api/_templates/layout.tsx | 35 ++++++++++ webapp/api/oauth/_templates/authorize.tsx | 84 +++++++++++++++++++++++ webapp/api/oauth/authorize.ts | 58 ++++++---------- 4 files changed, 175 insertions(+), 36 deletions(-) create mode 100644 webapp/api/_templates/button.tsx create mode 100644 webapp/api/_templates/layout.tsx create mode 100644 webapp/api/oauth/_templates/authorize.tsx diff --git a/webapp/api/_templates/button.tsx b/webapp/api/_templates/button.tsx new file mode 100644 index 0000000..82160fe --- /dev/null +++ b/webapp/api/_templates/button.tsx @@ -0,0 +1,34 @@ +import { html } from 'hono/html' + +interface ButtonProps { + text: string + variant?: 'primary' | 'secondary' + attributes?: Record +} + +const flattenAttributes = (attributes: Record) => + Object.entries(attributes) + .map(([key, value]) => `${key}="${value}"`) + .join(' ') + +const buttonVariants = { + primary: + 'bg-green-600 border-green-600 text-white hover:bg-white hover:text-green-600 transition-colors', + secondary: + 'bg-white border-green-600 text-green-600 hover:bg-green-600 hover:text-white transition-colors', +} as const + +export const _Button = ({ + text, + variant = 'primary', + attributes, +}: ButtonProps) => html` + +` diff --git a/webapp/api/_templates/layout.tsx b/webapp/api/_templates/layout.tsx new file mode 100644 index 0000000..f3385a1 --- /dev/null +++ b/webapp/api/_templates/layout.tsx @@ -0,0 +1,35 @@ +import { html } from 'hono/html' +import { HtmlEscapedString } from 'hono/utils/html' + +interface LayoutProps { + children: HtmlEscapedString | Promise + subtitle?: string +} + +const TITLE = 'Maximum Auth' +const ORG_NAME = '埼玉大学 プログラミングサークル Maximum' + +export const _Layout = ({ subtitle, children }: LayoutProps) => html` + + + + + + + ${subtitle ? `${subtitle} | ${TITLE}` : TITLE} + + + +
+ ${children} +
+
+ © ${new Date().getFullYear()} ${ORG_NAME} +
+ + +` diff --git a/webapp/api/oauth/_templates/authorize.tsx b/webapp/api/oauth/_templates/authorize.tsx new file mode 100644 index 0000000..d622c66 --- /dev/null +++ b/webapp/api/oauth/_templates/authorize.tsx @@ -0,0 +1,84 @@ +import { _Button } from 'api/_templates/button' +import { html } from 'hono/html' + +interface AuthorizeProps { + appName: string + appOwnerName: string + scopes: { name: string; description: string | null }[] + oauthFields: { + clientId: string + redirectUri: string + state: string + scope: string + token: string + nowUnixMs: number + } +} + +export const _Authorize = ({ + appName, + appOwnerName, + scopes, + oauthFields, +}: AuthorizeProps) => html` +
+
+

${appName}

+ + を承認しますか? + +
+
+

+ 承認すると ${appName} は以下の情報にアクセスできるようになります。 +

+
+ + + ${scopes.map( + data => html` + + + + + `, + )} + +
${data.name} + ${data.description} +
+
+
+ + + + + + +
+ ${_Button({ + text: '承認する', + variant: 'primary', + attributes: { type: 'submit', name: 'authorized', value: '1' }, + })} + ${_Button({ + text: '拒否する', + variant: 'secondary', + attributes: { type: 'submit', name: 'authorized', value: '0' }, + })} +
+

+ ${appOwnerName} によってリクエストされました。 +

+
+
+

+ ${new URL(oauthFields.redirectUri).origin} へリダイレクトします。 + このアドレスが意図しているものか確認してください。 +

+
+` diff --git a/webapp/api/oauth/authorize.ts b/webapp/api/oauth/authorize.ts index d3b2fe9..606ad76 100644 --- a/webapp/api/oauth/authorize.ts +++ b/webapp/api/oauth/authorize.ts @@ -1,11 +1,13 @@ import { importKey } from '@saitamau-maximum/auth/internal' +import { _Layout } from 'api/_templates/layout' import { Context, Hono } from 'hono' -import { html } from 'hono/html' import { validator } from 'hono/validator' import { HonoEnv } from 'load-context' import { generateAuthToken } from 'utils/auth-token.server' import { z } from 'zod' +import { _Authorize } from './_templates/authorize' + const app = new Hono() // 仕様はここ参照: https://github.com/saitamau-maximum/auth/issues/27 @@ -185,41 +187,25 @@ app.get( key: privateKey, }) - // TODO: デザインちゃんとする - // とりあえず GitHub OAuth のイメージで書いてる - const responseHtml = html` - - - Authorize ${clientInfo.name} | Maximum Auth - - -

${clientInfo.name} を承認しますか?

-
- 承認すると、 ${clientInfo.owner.displayName} による - ${clientInfo.name} はあなたのアカウント - (ここにログインユーザーの情報を入れる) - の以下の情報にアクセスできるようになります。 -
    - ${clientInfo.scopes.map( - data => - html`
  • ${data.scope.name}: ${data.scope.description}
  • `, - )} -
-
-
- - - - - - - - - ${new URL(redirectUri).origin} にリダイレクトします。 - このアドレスが意図しているものか確認してください。 -
- - ` + const responseHtml = _Layout({ + children: _Authorize({ + appName: clientInfo.name, + appOwnerName: clientInfo.owner.displayName, + scopes: clientInfo.scopes.map(data => ({ + name: data.scope.name, + description: data.scope.description, + })), + oauthFields: { + clientId, + redirectUri, + state, + scope, + token, + nowUnixMs, + }, + }), + subtitle: clientInfo.name, + }) c.header('Cache-Control', 'no-store') c.header('Pragma', 'no-cache')