design
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Body, Controller, Get, Param, Post, Req, Res, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Request, Response } from 'express';
|
||||
import type { Interaction } from 'oidc-provider';
|
||||
import { OidcProviderService } from './oidc-provider.service';
|
||||
|
||||
@Controller('interaction')
|
||||
@@ -14,38 +15,23 @@ export class OidcInteractionController {
|
||||
async view(@Param('uid') uid: string, @Req() request: Request, @Res() response: Response) {
|
||||
const details = await this.oidc.interactionDetails(request, response);
|
||||
if (details.uid !== uid) {
|
||||
response.status(400).send(this.page('Ungültige Anfrage', '<p>Die OIDC-Interaktion ist ungültig.</p>'));
|
||||
response.status(400).send(this.page('Ungueltige Anfrage', '<p>Die OIDC-Interaktion ist ungueltig.</p>'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.prompt.name === 'login') {
|
||||
|
||||
response.send(
|
||||
this.page(
|
||||
'Anmelden',
|
||||
this.loginForm(uid),
|
||||
),
|
||||
);
|
||||
response.send(this.page('Anmelden', this.loginForm(uid)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.prompt.name === 'consent') {
|
||||
console.log(details)
|
||||
response.send(
|
||||
this.page(
|
||||
'Zugriff erlauben',
|
||||
`
|
||||
<p>Client <strong>${this.escape(String(details.params.name ?? ''))}</strong> möchte Zugriff auf folgende Scopes:</p>
|
||||
<p class="scopes">${this.escape(String(details.params.scope ?? 'openid'))}</p>
|
||||
<form method="post" action="/interaction/${encodeURIComponent(uid)}/confirm">
|
||||
<button type="submit">Erlauben</button>
|
||||
</form>
|
||||
<form method="post" action="/interaction/${encodeURIComponent(uid)}/abort">
|
||||
<button class="secondary" type="submit">Ablehnen</button>
|
||||
</form>
|
||||
`,
|
||||
),
|
||||
);
|
||||
const clientId = String(details.params.client_id ?? '');
|
||||
if (clientId && (await this.oidc.isFirstPartyClient(clientId))) {
|
||||
await this.oidc.finishConsent(request, response, uid, { autoGranted: true });
|
||||
return;
|
||||
}
|
||||
|
||||
response.send(this.page('Zugriff erlauben', this.consentView(uid, details)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,7 +52,7 @@ export class OidcInteractionController {
|
||||
if (this.isInvalidCredentialsError(error)) {
|
||||
response
|
||||
.status(401)
|
||||
.send(this.page('Anmelden', this.loginForm(uid, username, 'Ungültige Zugangsdaten.')));
|
||||
.send(this.page('Anmelden', this.loginForm(uid, username, 'Ungueltige Zugangsdaten.')));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,6 +90,58 @@ export class OidcInteractionController {
|
||||
`;
|
||||
}
|
||||
|
||||
private consentView(uid: string, details: Interaction): string {
|
||||
const encodedUid = encodeURIComponent(uid);
|
||||
const clientId = String(details.params.client_id ?? '');
|
||||
const clientName = String(details.params.name ?? (clientId || 'Unbekannte Anwendung'));
|
||||
const redirectUri = String(details.params.redirect_uri ?? '');
|
||||
const scope = String(details.params.scope ?? 'openid');
|
||||
|
||||
return `
|
||||
<p class="intro">Die Anwendung <strong>${this.escape(clientName)}</strong> moechte auf dein Konto zugreifen.</p>
|
||||
${redirectUri ? `<dl class="consent-details"><div><dt>Weiterleitung</dt><dd>${this.escape(redirectUri)}</dd></div></dl>` : ''}
|
||||
<div class="scope-list" aria-label="Angeforderte Berechtigungen">
|
||||
${this.scopeItems(scope)}
|
||||
</div>
|
||||
<form method="post" action="/interaction/${encodedUid}/confirm">
|
||||
<button type="submit">Zugriff erlauben</button>
|
||||
</form>
|
||||
<form method="post" action="/interaction/${encodedUid}/abort">
|
||||
<button class="secondary" type="submit">Ablehnen</button>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
private scopeItems(scope: string): string {
|
||||
const scopes = scope
|
||||
.split(/\s+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return scopes
|
||||
.map(
|
||||
(item) => `
|
||||
<section class="scope-item">
|
||||
<strong>${this.escape(item)}</strong>
|
||||
<span>${this.escape(this.scopeDescription(item))}</span>
|
||||
</section>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
private scopeDescription(scope: string): string {
|
||||
const descriptions: Record<string, string> = {
|
||||
openid: 'Anmeldung per OpenID Connect bestaetigen.',
|
||||
profile: 'Profilinformationen wie Name und Anzeigename lesen.',
|
||||
email: 'E-Mail-Adresse lesen.',
|
||||
groups: 'Gruppenmitgliedschaften lesen.',
|
||||
offline_access: 'Laengerfristigen Zugriff ueber Refresh Tokens erlauben.',
|
||||
};
|
||||
|
||||
return descriptions[scope] ?? 'Diese Berechtigung wurde von der Anwendung angefordert.';
|
||||
}
|
||||
|
||||
private page(title: string, body: string): string {
|
||||
return `<!doctype html>
|
||||
<html lang="de">
|
||||
@@ -113,7 +151,7 @@ export class OidcInteractionController {
|
||||
<title>${this.escape(title)} - LDAP Portal</title>
|
||||
<style>
|
||||
body { background: #f5f7f9; color: #18202a; font-family: Inter, system-ui, sans-serif; margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 20px; }
|
||||
main { background: white; border: 1px solid #d8e0e7; border-radius: 8px; box-shadow: 0 16px 40px rgb(24 32 42 / 8%); max-width: 420px; padding: 28px; width: 100%; }
|
||||
main { background: white; border: 1px solid #d8e0e7; border-radius: 8px; box-shadow: 0 16px 40px rgb(24 32 42 / 8%); max-width: 460px; padding: 28px; width: 100%; }
|
||||
h1 { font-size: 1.45rem; margin: 0 0 22px; }
|
||||
form { display: grid; gap: 16px; margin-top: 16px; }
|
||||
label { display: grid; gap: 7px; font-weight: 700; }
|
||||
@@ -123,9 +161,16 @@ export class OidcInteractionController {
|
||||
a { color: #0f6b6e; font-weight: 700; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.form-link { margin: 16px 0 0; text-align: center; }
|
||||
.intro { color: #3a4551; line-height: 1.45; margin: 0 0 16px; }
|
||||
.message { background: #edf7f4; border: 1px solid #b8ddd3; border-radius: 6px; color: #24564f; margin: 0 0 16px; padding: 12px; }
|
||||
.message.error { background: #fff1f0; border-color: #efb5ae; color: #8d2b20; }
|
||||
.scopes { background: #edf2f5; border-radius: 6px; padding: 10px; word-break: break-word; }
|
||||
.consent-details { display: grid; gap: 10px; margin: 0 0 16px; }
|
||||
.consent-details div { display: grid; gap: 5px; }
|
||||
dt { color: #637083; font-size: 0.82rem; font-weight: 700; }
|
||||
dd { margin: 0; overflow-wrap: anywhere; }
|
||||
.scope-list { display: grid; gap: 8px; margin: 16px 0; }
|
||||
.scope-item { border: 1px solid #d8e0e7; border-radius: 6px; display: grid; gap: 4px; padding: 10px 12px; }
|
||||
.scope-item span { color: #637083; font-size: 0.9rem; line-height: 1.35; }
|
||||
</style>
|
||||
</head>
|
||||
<body><main><h1>${this.escape(title)}</h1>${body}</main></body>
|
||||
@@ -147,6 +192,6 @@ export class OidcInteractionController {
|
||||
}
|
||||
|
||||
private isInvalidCredentialsError(error: unknown): boolean {
|
||||
return error instanceof UnauthorizedException && error.message === 'Ungültige Zugangsdaten.';
|
||||
return error instanceof UnauthorizedException && error.message === 'Ungueltige Zugangsdaten.';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user