@aplons/auth 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,14 +17,14 @@ Eine Datei, vier Adressen:
17
17
 
18
18
  ```ts
19
19
  // app/api/auth/[...aplons]/route.ts
20
- import { handhabe } from "@aplons/auth/next";
20
+ import { createHandler } from "@aplons/auth/next";
21
21
 
22
- export const { GET, POST } = handhabe({
22
+ export const { GET, POST } = createHandler({
23
23
  issuer: process.env.APLONS_ISSUER!, // https://auth.aplons.com
24
24
  clientId: process.env.APLONS_CLIENT_ID!,
25
25
  clientSecret: process.env.APLONS_CLIENT_SECRET,
26
26
  redirectUri: process.env.APLONS_REDIRECT_URI!,
27
- nachAnmeldung: "/",
27
+ afterLogin: "/",
28
28
  });
29
29
  ```
30
30
 
@@ -32,28 +32,29 @@ Das ergibt:
32
32
 
33
33
  | Adresse | wozu |
34
34
  |---|---|
35
- | `/api/auth/login` | Anmeldung starten. `?weiter=/pfad` merkt sich, wohin danach. |
35
+ | `/api/auth/login` | Anmeldung starten. `?next=/pfad` merkt sich, wohin danach. |
36
36
  | `/api/auth/callback` | Rückkehr von Aplons. Hier ist nichts zu tun. |
37
37
  | `/api/auth/logout` | Abmelden — hier **und** bei Aplons. |
38
38
  | `/api/auth/me` | Wer angemeldet ist; erneuert das Token still, wenn nötig. |
39
+ | `/api/auth/branding` | Logo, Farben und Eckenradius des Mandanten. |
39
40
 
40
41
  Ein Anmeldeknopf ist dann ein Link:
41
42
 
42
43
  ```tsx
43
- <a href="/api/auth/login?weiter=/rechnungen">Anmelden</a>
44
+ <a href="/api/auth/login?next=/rechnungen">Anmelden</a>
44
45
  ```
45
46
 
46
47
  Die Sitzung in einer eigenen Route lesen:
47
48
 
48
49
  ```ts
49
- const { sitzungAus, auth } = handhabe({ /* … */ });
50
+ const { getSession, auth } = createHandler({ /* … */ });
50
51
 
51
52
  export async function GET(request: Request) {
52
- const sitzung = await sitzungAus(request);
53
- if (!sitzung) return new Response("nicht angemeldet", { status: 401 });
53
+ const session = await getSession(request);
54
+ if (!session) return new Response("nicht angemeldet", { status: 401 });
54
55
 
55
- const profil = await auth.profil(sitzung.accessToken);
56
- return Response.json(profil);
56
+ const user = await auth.userInfo(session.accessToken);
57
+ return Response.json(user);
57
58
  }
58
59
  ```
59
60
 
@@ -70,19 +71,19 @@ const auth = new AplonsAuth({
70
71
  });
71
72
 
72
73
  // 1. Hinschicken
73
- const { url, verifier, state, nonce } = await auth.start();
74
+ const { url, verifier, state, nonce } = await auth.startLogin();
74
75
  // verifier, state und nonce bis zur Rückkehr aufbewahren
75
76
 
76
77
  // 2. Zurückkommen
77
- const sitzung = await auth.rueckkehr({ url: anfrageUrl, verifier, state, nonce });
78
- sitzung.accessToken;
79
- sitzung.claims?.email;
78
+ const session = await auth.completeLogin({ url: requestUrl, verifier, state, nonce });
79
+ session.accessToken;
80
+ session.claims?.email;
80
81
 
81
82
  // 3. Erneuern
82
- const frisch = await auth.erneuern(sitzung.refreshToken!);
83
+ const fresh = await auth.refresh(session.refreshToken!);
83
84
 
84
85
  // 4. Abmelden
85
- const abmelden = await auth.abmeldeUrl({ danach: "https://app.example.de/" });
86
+ const logout = await auth.logoutUrl({ returnTo: "https://app.example.de/" });
86
87
  ```
87
88
 
88
89
  **`verifier`, `state` und `nonce` gehören in ein kurzlebiges, `httpOnly`
@@ -90,10 +91,53 @@ gesetztes Cookie — nicht in den `localStorage`.** Was dort liegt, liest jedes
90
91
  Skript auf der Seite, und mit dem Verifier lässt sich ein abgefangener
91
92
  Anmeldecode einlösen. Der Next-Teil oben macht das schon richtig.
92
93
 
94
+ ## Aussehen wie die Firma
95
+
96
+ Eine angebundene Anwendung meldet Leute nicht nur an, sie zeigt ihnen danach
97
+ etwas. Ein Intranet-Hub, ein internes Werkzeug, ein Kundenportal: alle sollen
98
+ aussehen wie die Firma, für die sie gebaut sind. Das Logo dafür ein zweites
99
+ Mal einzupflegen ist die Art Arbeit, die genau einmal gemacht und beim
100
+ nächsten Rebranding vergessen wird — dann trägt die Anmeldung das neue Logo
101
+ und der Hub daneben das alte.
102
+
103
+ Also von dort, wo es ohnehin gepflegt wird:
104
+
105
+ ```ts
106
+ const branding = await auth.branding(session.accessToken);
107
+
108
+ branding.logoUrl; // vollständige Adresse, direkt für ein <img>
109
+ branding.colors.primary; // "#4882fe"
110
+ branding.radius; // 8
111
+ branding.tenant.name; // "Muster GmbH"
112
+ ```
113
+
114
+ Als CSS-Variablen, wenn die ganze Oberfläche sie tragen soll:
115
+
116
+ ```tsx
117
+ import { brandingCss } from "@aplons/auth";
118
+
119
+ <style>{brandingCss(branding)}</style>
120
+ <button style={{ background: "var(--aplons-primary)", borderRadius: "var(--aplons-radius)" }}>
121
+ ```
122
+
123
+ Mit Next.js geht es auch ohne eigenen Aufruf: `/api/auth/branding` liefert
124
+ dasselbe für die angemeldete Sitzung, und `getBranding(request)` holt es in
125
+ einer eigenen Route.
126
+
127
+ Zwei Dinge müssen dafür stimmen, und die Fehlermeldung sagt welches:
128
+
129
+ | `code` | heißt |
130
+ |---|---|
131
+ | `insufficient_scope` | Dein Token trägt den Scope `branding` nicht — trag ihn bei der Anwendung ein und fordere ihn an (`scope: ["openid", "profile", "email", "branding"]`). |
132
+ | `branding_not_shared` | Der Mandant gibt sein Erscheinungsbild nicht heraus. Das steht in **seinen** Richtlinien und ist ab Werk aus; nur er kann das ändern. |
133
+
134
+ **Speichere das Ergebnis zwischen.** Ein Logo wechselt selten, und eine Seite,
135
+ die es bei jedem Aufruf neu holt, wartet dafür jedes Mal auf das Netz.
136
+
93
137
  ## Eine API dahinter absichern
94
138
 
95
139
  ```ts
96
- const claims = await auth.pruefeToken(token);
140
+ const claims = await auth.verifyAccessToken(token);
97
141
  claims.sub; // das Konto
98
142
  claims.tid; // der Mandant
99
143
  claims.rls; // Rollen im Mandanten
@@ -123,9 +167,9 @@ Meldung für den Menschen:
123
167
  import { AplonsError } from "@aplons/auth";
124
168
 
125
169
  try {
126
- await auth.rueckkehr({ /* … */ });
127
- } catch (fehler) {
128
- if (fehler instanceof AplonsError && fehler.code === "invalid_grant") {
170
+ await auth.completeLogin({ /* … */ });
171
+ } catch (error) {
172
+ if (error instanceof AplonsError && error.code === "invalid_grant") {
129
173
  // meistens: die Rückkehr wurde zweimal ausgeführt
130
174
  }
131
175
  }
@@ -142,9 +186,21 @@ Häufig:
142
186
 
143
187
  ## Bereiche
144
188
 
145
- `openid`, `profile`, `email`, `phone`, `roles`, `app_profile`. Voreinstellung
146
- sind die ersten drei. `roles` liefert die Rollen des Kontos **in dieser
147
- Anwendung**, `app_profile` deren eigene Felder — beide über `auth.profil()`.
189
+ `openid`, `profile`, `email`, `phone`, `roles`, `app_profile`, `branding`,
190
+ `offline_access`. Voreinstellung sind die ersten drei. `roles` liefert die
191
+ Rollen des Kontos **in dieser Anwendung**, `app_profile` deren eigene Felder —
192
+ beide über `auth.userInfo()`. `branding` das Erscheinungsbild des Mandanten,
193
+ über `auth.branding()`.
194
+
195
+ `offline_access` darf jeder Client anfordern, auch ohne ihn eingetragen zu
196
+ haben, und bewirkt nichts: ein Refresh-Token kommt beim Tausch des
197
+ Anmeldecodes ohnehin immer zurück. Er steht hier, weil er der übliche Weg
198
+ ist, danach zu fragen, und manche Bibliotheken ihn von sich aus mitschicken.
199
+
200
+ Alle anderen Bereiche muss die Anwendung im Mandanten eingetragen haben.
201
+ Fehlt einer, kommt von `/oauth/authorize` ein `invalid_scope` zurück, dessen
202
+ Text zwischen „kennt der Server nicht" und „diese Anwendung hat ihn nicht
203
+ eingetragen" unterscheidet.
148
204
 
149
205
  ## Sitzungen ablegen
150
206
 
@@ -153,12 +209,16 @@ Speicher aus, hat aber zwei Grenzen: 4 KB, und ein Widerruf wirkt erst, wenn
153
209
  das Zugriffstoken abläuft. Wer eine Datenbank hat, gibt seine eigene Ablage an:
154
210
 
155
211
  ```ts
156
- handhabe({
212
+ createHandler({
157
213
  /* … */
158
- speicher: {
159
- async lies(id) { /* … */ },
160
- async schreib(id, sitzung) { /* … */ },
161
- async loesche(id) { /* … */ },
214
+ store: {
215
+ async read(id) { /* … */ },
216
+ async write(id, session) { /* … */ },
217
+ async delete(id) { /* … */ },
162
218
  },
163
219
  });
164
220
  ```
221
+
222
+ Alle drei werden gebraucht: `delete` beim Abmelden, `write` bei jeder stillen
223
+ Erneuerung — und zwar unter **derselben** Kennung, damit aus einer Sitzung
224
+ nicht alle zehn Minuten eine weitere Zeile wird.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Das Erscheinungsbild des Mandanten — Logo, Farben, Eckenradius.
3
+ *
4
+ * Eine angebundene Anwendung meldet Leute nicht nur an, sie zeigt ihnen
5
+ * danach etwas. Ein Intranet-Hub, ein internes Werkzeug, ein Kundenportal:
6
+ * alle sollen aussehen wie die Firma, für die sie gebaut sind. Das Logo dafür
7
+ * ein zweites Mal einzupflegen ist die Art von Arbeit, die genau einmal
8
+ * gemacht und danach beim Rebranding vergessen wird — dann trägt die
9
+ * Anmeldung das neue Logo und der Hub daneben das alte.
10
+ *
11
+ * Also von dort, wo es ohnehin gepflegt wird:
12
+ *
13
+ * const branding = await auth.branding(session.accessToken);
14
+ * branding.logoUrl;
15
+ * branding.colors.primary;
16
+ *
17
+ * Zwei Dinge müssen dafür stimmen, und die Fehlermeldung sagt welches:
18
+ * die Anwendung braucht den Bereich `branding`, und der Mandant muss das
19
+ * Weitergeben in seinen Richtlinien erlaubt haben. Letzteres ist ab Werk aus.
20
+ */
21
+ /** Was `auth.branding()` zurückgibt. */
22
+ export type Branding = {
23
+ tenant: {
24
+ slug: string;
25
+ /** Anzeigename des Mandanten. */
26
+ name: string;
27
+ /** Der oberste Mandant darüber — für „bereitgestellt von". */
28
+ rootName: string;
29
+ };
30
+ logoUrl?: string;
31
+ /** Fassung für dunkle Oberflächen, falls hinterlegt. */
32
+ logoUrlDark?: string;
33
+ faviconUrl?: string;
34
+ /**
35
+ * Gesetzt heißt: der Mandant legt hell oder dunkel fest. Fehlt der Wert,
36
+ * entscheidet die Anwendung — in aller Regel: was der Besucher eingestellt
37
+ * hat.
38
+ */
39
+ colorScheme?: "light" | "dark";
40
+ colors: {
41
+ primary: string;
42
+ /** Was auf der Primärfarbe lesbar ist. */
43
+ primaryText: string;
44
+ background: string;
45
+ surface: string;
46
+ text: string;
47
+ mutedText: string;
48
+ border: string;
49
+ danger: string;
50
+ };
51
+ /** Eckenradius in Pixeln. */
52
+ radius: number;
53
+ logo: {
54
+ height: number;
55
+ maxWidth: number;
56
+ /** Steht der Name neben dem Logo? Fehlt der Wert: Sache der Anwendung. */
57
+ showText?: boolean;
58
+ /** Was dort steht, wenn nicht der Mandantenname. */
59
+ text?: string;
60
+ };
61
+ imprintUrl?: string;
62
+ privacyUrl?: string;
63
+ supportEmail?: string;
64
+ };
65
+ /**
66
+ * Die Farben und der Eckenradius als CSS-Variablen.
67
+ *
68
+ * Gedacht für ein `<style>` im Kopf der Seite oder für ein `style`-Attribut
69
+ * am äußersten Element:
70
+ *
71
+ * <style>{brandingCss(branding)}</style>
72
+ * …
73
+ * <button style={{ background: "var(--aplons-primary)" }}>
74
+ *
75
+ * Nur Farben und Radius: ein Logo ist ein Bild und gehört in ein `<img>`, wo
76
+ * es einen Alternativtext bekommt, statt als Hintergrund in einem Stylesheet
77
+ * zu verschwinden.
78
+ */
79
+ export declare function brandingCss(branding: Branding, options?: {
80
+ /** Woran die Variablen hängen. Voreinstellung `:root`. */
81
+ selector?: string;
82
+ }): string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Das Erscheinungsbild des Mandanten — Logo, Farben, Eckenradius.
3
+ *
4
+ * Eine angebundene Anwendung meldet Leute nicht nur an, sie zeigt ihnen
5
+ * danach etwas. Ein Intranet-Hub, ein internes Werkzeug, ein Kundenportal:
6
+ * alle sollen aussehen wie die Firma, für die sie gebaut sind. Das Logo dafür
7
+ * ein zweites Mal einzupflegen ist die Art von Arbeit, die genau einmal
8
+ * gemacht und danach beim Rebranding vergessen wird — dann trägt die
9
+ * Anmeldung das neue Logo und der Hub daneben das alte.
10
+ *
11
+ * Also von dort, wo es ohnehin gepflegt wird:
12
+ *
13
+ * const branding = await auth.branding(session.accessToken);
14
+ * branding.logoUrl;
15
+ * branding.colors.primary;
16
+ *
17
+ * Zwei Dinge müssen dafür stimmen, und die Fehlermeldung sagt welches:
18
+ * die Anwendung braucht den Bereich `branding`, und der Mandant muss das
19
+ * Weitergeben in seinen Richtlinien erlaubt haben. Letzteres ist ab Werk aus.
20
+ */
21
+ /** Die Namen, unter denen `brandingCss` die Werte ablegt. */
22
+ const VARIABLES = [
23
+ ["primary", "primary"],
24
+ ["primaryText", "primary-text"],
25
+ ["background", "background"],
26
+ ["surface", "surface"],
27
+ ["text", "text"],
28
+ ["mutedText", "muted-text"],
29
+ ["border", "border"],
30
+ ["danger", "danger"],
31
+ ];
32
+ /**
33
+ * Genau eine Hex-Farbe, sonst nichts.
34
+ *
35
+ * Der Server prüft das schon. Hier wird es trotzdem geprüft, weil das
36
+ * Ergebnis in ein `<style>` geschrieben wird: käme dort jemals ein anderer
37
+ * Text an — durch einen Zwischenspeicher, einen Vermittlungsserver, eine
38
+ * spätere Änderung am Server —, stünde er in einem Stylesheet, und ein
39
+ * Stylesheet lässt sich zum Auslesen von Eingaben missbrauchen. Eine Prüfung,
40
+ * die an zwei Stellen steht, kostet nichts; eine, die nur an einer steht,
41
+ * kostet genau dann etwas, wenn diese eine sich ändert.
42
+ */
43
+ function hex(value) {
44
+ return typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)
45
+ ? value.toLowerCase()
46
+ : null;
47
+ }
48
+ /**
49
+ * Die Farben und der Eckenradius als CSS-Variablen.
50
+ *
51
+ * Gedacht für ein `<style>` im Kopf der Seite oder für ein `style`-Attribut
52
+ * am äußersten Element:
53
+ *
54
+ * <style>{brandingCss(branding)}</style>
55
+ * …
56
+ * <button style={{ background: "var(--aplons-primary)" }}>
57
+ *
58
+ * Nur Farben und Radius: ein Logo ist ein Bild und gehört in ein `<img>`, wo
59
+ * es einen Alternativtext bekommt, statt als Hintergrund in einem Stylesheet
60
+ * zu verschwinden.
61
+ */
62
+ export function brandingCss(branding, options) {
63
+ const declarations = [];
64
+ for (const [key, name] of VARIABLES) {
65
+ const value = hex(branding.colors?.[key]);
66
+ if (value)
67
+ declarations.push(`--aplons-${name}: ${value};`);
68
+ }
69
+ const radius = Number(branding.radius);
70
+ if (Number.isFinite(radius)) {
71
+ declarations.push(`--aplons-radius: ${Math.min(Math.max(Math.round(radius), 0), 64)}px;`);
72
+ }
73
+ if (declarations.length === 0)
74
+ return "";
75
+ // Der Selektor kommt vom Aufrufer, nicht vom Server — trotzdem ohne
76
+ // geschweifte Klammern, damit ein Tippfehler nicht mitten im Block einen
77
+ // neuen aufmacht.
78
+ const selector = (options?.selector ?? ":root").replace(/[{}]/g, "");
79
+ return `${selector} { ${declarations.join(" ")} }`;
80
+ }
package/dist/client.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Die Anbindung an Aplons.
2
+ * The connection to Aplons.
3
3
  *
4
- * Vier Schritte, mehr ist es nicht: Anmeldung starten, Rückkehr entgegen-
5
- * nehmen, Token erneuern, abmelden. Dazu das Prüfen eines Tokens für den,
6
- * der eine API dahinter absichert.
4
+ * Four steps, no more: start the login, complete it, refresh the tokens, log
5
+ * out. Plus verifying a token, for whoever has an API behind it.
7
6
  *
8
- * Alles, was der Ablauf sonst noch braucht welcher Endpunkt wo liegt,
9
- * welche Schlüssel gerade gültig sind —, holt sich das Paket selbst. Wer nur
10
- * `issuer` und `clientId` kennt, ist fertig.
7
+ * Everything else the flow needs which endpoint lives where, which keys are
8
+ * currently valid the package fetches itself. If you know `issuer` and
9
+ * `clientId`, you are done.
11
10
  */
12
- import { type Metadaten } from "./discovery.js";
13
- import type { AccessTokenClaims, Anmeldevorgang, AplonsOptions, Profil, Sitzung } from "./types.js";
11
+ import { type Metadata } from "./discovery.js";
12
+ import type { Branding } from "./branding.js";
13
+ import type { AccessTokenClaims, AplonsOptions, AuthorizationRequest, Session, UserInfo } from "./types.js";
14
14
  export declare class AplonsAuth {
15
15
  #private;
16
16
  readonly issuer: string;
@@ -19,75 +19,98 @@ export declare class AplonsAuth {
19
19
  readonly scope: string[];
20
20
  constructor(options: AplonsOptions);
21
21
  /**
22
- * Die Endpunkte, einmal geholt und dann behalten.
22
+ * The endpoints, fetched once and then kept.
23
23
  *
24
- * Als Versprechen zwischengespeichert, nicht als Ergebnis: sonst holen
25
- * zehn gleichzeitige Anfragen beim Start zehnmal dieselbe Datei.
24
+ * Cached as a promise, not as a result: otherwise ten concurrent requests
25
+ * at startup fetch the same document ten times.
26
26
  */
27
- metadaten(): Promise<Metadaten>;
27
+ metadata(): Promise<Metadata>;
28
28
  /**
29
- * Schritt 1: Wohin der Browser geschickt wird.
29
+ * Step 1: where to send the browser.
30
30
  *
31
- * `verifier` und `state` müssen bis zur Rückkehr aufbewahrt werden — in
32
- * einem kurzlebigen, `httpOnly`-Cookie, nicht im localStorage: was dort
33
- * liegt, liest jedes Skript auf der Seite.
31
+ * `verifier`, `state` and `nonce` have to survive until the callback — in a
32
+ * short-lived, `httpOnly` cookie, not in localStorage: whatever sits there
33
+ * is readable by every script on the page.
34
34
  */
35
- start(options?: {
36
- /** Überschreibt die Bereiche aus dem Konstruktor. */
35
+ startLogin(options?: {
36
+ /** Overrides the scopes from the constructor. */
37
37
  scope?: string[];
38
- /** Wird unverändert zurückgegebenetwa die Seite, die aufgerufen war. */
39
- zusatz?: Record<string, string>;
40
- /** Erzwingt die Anmeldemaske, auch wenn schon eine Sitzung besteht. */
41
- erneutAnmelden?: boolean;
42
- /** Setzt die Anmeldeseite auf diesen Mandanten. */
38
+ /** Passed through unchangede.g. the page that was being opened. */
39
+ extraParams?: Record<string, string>;
40
+ /** Forces the login screen even when a session already exists. */
41
+ forceLogin?: boolean;
42
+ /** Puts the login screen on this tenant. */
43
43
  tenant?: string;
44
- }): Promise<Anmeldevorgang>;
44
+ }): Promise<AuthorizationRequest>;
45
45
  /**
46
- * Schritt 2: Die Rückkehr.
46
+ * Step 2: the callback.
47
47
  *
48
- * Nimmt den vollständigen URL entgegen, mit dem der Browser zurückkam, und
49
- * die beiden Werte aus Schritt 1.
48
+ * Takes the full URL the browser came back with, plus the values from
49
+ * step 1.
50
50
  */
51
- rueckkehr(options: {
52
- /** Der Aufruf-URL, komplett. */
51
+ completeLogin(options: {
52
+ /** The incoming URL, complete. */
53
53
  url: string | URL;
54
54
  verifier: string;
55
- /** Der State aus Schritt 1 — wird gegen den im URL geprüft. */
55
+ /** The state from step 1 — checked against the one in the URL. */
56
56
  state: string;
57
- /** Der Nonce aus Schritt 1, wenn ein ID-Token erwartet wird. */
57
+ /** The nonce from step 1, when an ID token is expected. */
58
58
  nonce?: string;
59
- }): Promise<Sitzung>;
60
- /** Schritt 3: Ein abgelaufenes Zugriffstoken gegen ein frisches tauschen. */
61
- erneuern(refreshToken: string): Promise<Sitzung>;
59
+ }): Promise<Session>;
60
+ /** Step 3: trade an expired access token for a fresh one. */
61
+ refresh(refreshToken: string): Promise<Session>;
62
62
  /**
63
- * Schritt 4: Abmeldenbei Aplons, nicht nur hier.
63
+ * Step 4: log out at Aplons, not just here.
64
64
  *
65
- * Nur die eigene Sitzung zu löschen genügt nicht: die Sitzung bei Aplons
66
- * bliebe bestehen, und die nächste Anmeldung liefe ohne Passwort durch.
67
- * Auf einem geteilten Rechner ist das der Unterschied zwischen abgemeldet
68
- * und scheinbar abgemeldet.
65
+ * Clearing your own session is not enough: the session at Aplons would
66
+ * survive, and the next login would sail through without a password. On a
67
+ * shared machine that is the difference between signed out and apparently
68
+ * signed out.
69
69
  */
70
- abmeldeUrl(options?: {
71
- /** Wohin Aplons nach dem Abmelden zurückschickt. Muss hinterlegt sein. */
72
- danach?: string;
73
- /** Das ID-Token der Sitzung; damit weiß Aplons, wen es abmeldet. */
70
+ logoutUrl(options?: {
71
+ /** Where Aplons sends the browser after logging out. Must be registered. */
72
+ returnTo?: string;
73
+ /** The session's ID token; it tells Aplons who is being logged out. */
74
74
  idToken?: string;
75
75
  }): Promise<string>;
76
76
  /**
77
- * Ein Zugriffstoken prüfenfür den, der eine eigene API dahinter hat.
77
+ * Verify an access token for whoever has their own API behind it.
78
78
  *
79
- * Geprüft wird gegen die öffentlichen Schlüssel von Aplons, ohne Rückfrage
80
- * bei jedem Aufruf: Unterschrift, Aussteller und Ablauf. Das ist der
81
- * Unterschied zwischen „das Token sieht echt aus" und „das Token ist echt".
79
+ * Checked against the public keys of Aplons, without a round trip on every
80
+ * call: signature, issuer and expiry. That is the difference between "the
81
+ * token looks real" and "the token is real".
82
82
  */
83
- pruefeToken(token: string): Promise<AccessTokenClaims>;
84
- /** Die Angaben zum angemeldeten Konto, so weit die Bereiche es hergeben. */
85
- profil(accessToken: string): Promise<Profil>;
83
+ verifyAccessToken(token: string): Promise<AccessTokenClaims>;
86
84
  /**
87
- * Ein Refresh-Token entwerten.
85
+ * Das Erscheinungsbild des Mandanten — Logo, Farben, Eckenradius.
88
86
  *
89
- * Gehört zum Abmelden dazu: ein Token, das noch dreißig Tage gilt, wird
90
- * vom Löschen des Cookies nicht ungültig.
87
+ * Ein Aufruf, damit eine eingebundene Oberfläche aussieht wie die Firma,
88
+ * für die sie gebaut ist, statt deren Logo ein zweites Mal zu pflegen:
89
+ *
90
+ * const branding = await auth.branding(session.accessToken);
91
+ *
92
+ * Welcher Mandant, steht im Token — nicht im Aufruf. Ein Konto bekommt
93
+ * damit immer genau das Erscheinungsbild, das es auf der Anmeldeseite auch
94
+ * gesehen hat.
95
+ *
96
+ * Es lohnt sich, das Ergebnis zwischenzuspeichern: ein Logo wechselt selten,
97
+ * und eine Seite, die es bei jedem Aufruf neu holt, wartet dafür jedes Mal
98
+ * auf eine Antwort aus dem Netz.
99
+ *
100
+ * Zwei Absagen sind möglich und meinen Verschiedenes: `insufficient_scope`
101
+ * heißt, das Token trägt den Bereich `branding` nicht — dann fehlt er in den
102
+ * `scope`s dieser Anwendung. `branding_not_shared` heißt, der Mandant gibt
103
+ * sein Erscheinungsbild nicht heraus; das steht in seinen Richtlinien und
104
+ * ist ab Werk aus.
105
+ */
106
+ branding(accessToken: string): Promise<Branding>;
107
+ /** The details of the signed-in account, as far as the scopes allow. */
108
+ userInfo(accessToken: string): Promise<UserInfo>;
109
+ /**
110
+ * Revoke a refresh token.
111
+ *
112
+ * Part of logging out: a token still valid for thirty days does not become
113
+ * invalid because a cookie was deleted.
91
114
  */
92
- widerrufen(refreshToken: string): Promise<void>;
115
+ revoke(refreshToken: string): Promise<void>;
93
116
  }