@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/dist/errors.js CHANGED
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Ein Fehler, der sagt, was zu tun ist.
2
+ * An error that says what to do about it.
3
3
  *
4
- * OAuth antwortet mit Kennungen wie `invalid_grant` — richtig für eine
5
- * Maschine, nutzlos für den, der um drei Uhr nachts in ein Log sieht. Jeder
6
- * Fehler hier trägt deshalb beides: die Kennung für den Code und einen Satz
7
- * für den Menschen.
4
+ * OAuth answers with identifiers like `invalid_grant` — right for a machine,
5
+ * useless for the person reading a log at three in the morning. Every error
6
+ * here carries both: the identifier for the code and a sentence for the
7
+ * human.
8
8
  */
9
9
  export class AplonsError extends Error {
10
- /** Die OAuth-Kennung, etwa `invalid_grant`. */
10
+ /** The OAuth identifier, e.g. `invalid_grant`. */
11
11
  code;
12
- /** Der HTTP-Status, wenn der Fehler von einer Antwort kam. */
12
+ /** The HTTP status, when the error came from a response. */
13
13
  status;
14
- /** Was der Server dazu geschrieben hat. */
14
+ /** What the server wrote about it. */
15
15
  description;
16
16
  constructor(options) {
17
17
  super(options.message, { cause: options.cause });
@@ -22,57 +22,61 @@ export class AplonsError extends Error {
22
22
  }
23
23
  }
24
24
  /**
25
- * `invalid_grant` heißt bei jedem Ablauf etwas anderes.
25
+ * `invalid_grant` means something different in every flow.
26
26
  *
27
- * Beide Ursachen unter denselben Satz zu stellen war ein Fehler: wer eine
28
- * fehlgeschlagene Erneuerung untersuchte, las etwas über einen Anmeldecode,
29
- * den es in diesem Ablauf gar nicht gibt und suchte an der falschen Stelle.
27
+ * Putting both causes under the same sentence was a mistake: anyone looking
28
+ * into a failed refresh read about an authorization code that does not exist
29
+ * in that flow and went looking in the wrong place.
30
30
  */
31
31
  const INVALID_GRANT = {
32
- code: "Der Anmeldecode wurde schon eingelöst, ist abgelaufen, oder der " +
33
- "code_verifier passt nicht zum code_challenge. Meistens: die Rückkehr " +
34
- "wurde zweimal ausgeführt, etwa weil der Browser die Seite neu geladen hat.",
35
- refresh: "Das Refresh-Token gilt nicht mehr. Entweder ist es abgelaufen, es wurde " +
36
- "beim Abmelden entwertet, oder es wurde schon einmal eingetauschtjedes " +
37
- "Erneuern gibt ein neues aus und macht das alte ungültig. Wer das alte " +
38
- "aufbewahrt und noch einmal schickt, sieht genau diesen Fehler. In allen " +
39
- "Fällen hilft nur eine neue Anmeldung.",
40
- allgemein: "Die vorgelegte Berechtigung gilt nicht mehr. Eine neue Anmeldung hilft.",
32
+ code: "The authorization code was already redeemed, has expired, or the " +
33
+ "code_verifier does not match the code_challenge. Usually: the callback " +
34
+ "ran twice, for instance because the browser reloaded the page.",
35
+ refresh: "The refresh token is no longer valid. Either it expired, it was revoked " +
36
+ "at logout, or it has already been exchangedevery refresh issues a new " +
37
+ "one and invalidates the old. Keeping the old one and sending it again " +
38
+ "produces exactly this error. In all cases only a fresh login helps.",
39
+ generic: "The grant presented is no longer valid. A fresh login helps.",
41
40
  };
42
- /** Die häufigsten Kennungen, in Worte gefasst. */
43
- const ERKLAERT = {
44
- invalid_client: "Client-ID oder Client-Secret stimmen nicht. Bei einer öffentlichen " +
45
- "Anwendung darf gar kein Secret mitgeschickt werden.",
46
- invalid_request: "Der Anfrage fehlt etwas oder sie enthält etwas Widersprüchliches.",
47
- unauthorized_client: "Diese Anwendung darf diesen Ablauf nicht verwenden.",
48
- access_denied: "Die Anmeldung wurde abgebrochen oder das Konto hat auf diese Anwendung " +
49
- "keinen Zugriff.",
50
- invalid_scope: "Mindestens einer der angeforderten Bereiche ist unbekannt.",
51
- server_error: "Bei Aplons ist etwas schiefgegangen. Siehe status.aplons.com.",
41
+ /** The most common identifiers, put into words. */
42
+ const EXPLANATIONS = {
43
+ invalid_client: "Client ID or client secret are wrong. A public application must not " +
44
+ "send a secret at all.",
45
+ invalid_request: "The request is missing something, or contradicts itself.",
46
+ unauthorized_client: "This application may not use this flow.",
47
+ access_denied: "The login was cancelled, or the account has no access to this application.",
48
+ invalid_scope: "At least one of the requested scopes is unknown.",
49
+ insufficient_scope: "The token does not carry the scope this endpoint requires. Register the " +
50
+ "scope for the application and ask for it at login — an access token only " +
51
+ "ever holds what was granted.",
52
+ branding_not_shared: "The tenant does not hand out its branding to applications. That is a rule " +
53
+ "in its own policies and off by default; nothing on your side can change " +
54
+ "it, only the tenant can.",
55
+ server_error: "Something went wrong at Aplons. See status.aplons.com.",
52
56
  };
53
- /** Aus einer fehlgeschlagenen Antwort einen brauchbaren Fehler machen. */
54
- export async function ausAntwort(antwort, wobei, ablauf = "allgemein") {
55
- let code = "http_" + antwort.status;
57
+ /** Turn a failed response into something usable. */
58
+ export async function fromResponse(response, what, flow = "generic") {
59
+ let code = "http_" + response.status;
56
60
  let description;
57
61
  try {
58
- const koerper = (await antwort.json());
59
- if (koerper.error)
60
- code = koerper.error;
61
- description = koerper.error_description ?? koerper.message;
62
+ const body = (await response.json());
63
+ if (body.error)
64
+ code = body.error;
65
+ description = body.error_description ?? body.message;
62
66
  }
63
67
  catch {
64
- // Keine JSON-Antwort. Dann bleibt der Status die ganze Auskunftwas
65
- // bei einem Proxy dazwischen der häufigere Fall ist als bei uns.
68
+ // Not a JSON response. Then the status is all there iswhich happens
69
+ // more often with a proxy in between than with us.
66
70
  }
67
- const erklaerung = code === "invalid_grant" ? INVALID_GRANT[ablauf] : ERKLAERT[code];
71
+ const explanation = code === "invalid_grant" ? INVALID_GRANT[flow] : EXPLANATIONS[code];
68
72
  return new AplonsError({
69
73
  code,
70
- status: antwort.status,
74
+ status: response.status,
71
75
  description,
72
76
  message: [
73
- `${wobei} fehlgeschlagen (${code}).`,
74
- erklaerung,
75
- description && description !== erklaerung ? `Server: ${description}` : null,
77
+ `${what} failed (${code}).`,
78
+ explanation,
79
+ description && description !== explanation ? `Server: ${description}` : null,
76
80
  ]
77
81
  .filter(Boolean)
78
82
  .join(" "),
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  /**
2
- * @aplons/auth — Anmeldung über Aplons in eigenen Anwendungen.
2
+ * @aplons/auth — sign in through Aplons from your own applications.
3
3
  *
4
- * Der Kern, ohne Rahmenwerk: läuft in Node, im Browser, am Rand und in einem
5
- * Worker. Für Next.js gibt es zusätzlich `@aplons/auth/next`, das die
6
- * Cookie-Arbeit abnimmt.
4
+ * The core, framework-free: runs in Node, in the browser, at the edge and in
5
+ * a worker. For Next.js there is also `@aplons/auth/next`, which takes the
6
+ * cookie work off your hands.
7
7
  *
8
- * Eine einzige Abhängigkeit, `jose`, und die nur zum Prüfen von Unterschriften
9
- * dieselbe, die der Aplons-Server selbst benutzt. Alles andere macht die
10
- * eingebaute Web-Crypto.
8
+ * A single dependency, `jose`, and only for verifying signatures the same
9
+ * one the Aplons server itself uses. Everything else is built-in Web Crypto.
11
10
  */
12
11
  export { AplonsAuth } from "./client.js";
13
- export { AplonsError } from "./errors.js";
14
- export { base64url, createChallenge, createState, createVerifier, gleich, } from "./pkce.js";
15
- export { pruefeIdToken, pruefeZugriffstoken, schluesselFuer } from "./verify.js";
16
- export { holeMetadaten, type Metadaten } from "./discovery.js";
17
- export type { AccessTokenClaims, Anmeldevorgang, AplonsOptions, IdTokenClaims, Profil, Sitzung, } from "./types.js";
12
+ export { AplonsError, type Flow } from "./errors.js";
13
+ export { base64url, createChallenge, createState, createVerifier, timingSafeEqual, } from "./pkce.js";
14
+ export { jwksFor, verifyAccessToken, verifyIdToken, type KeySource } from "./verify.js";
15
+ export { brandingCss, type Branding } from "./branding.js";
16
+ export { fetchMetadata, type Metadata } from "./discovery.js";
17
+ export type { AccessTokenClaims, AplonsOptions, AuthorizationRequest, IdTokenClaims, Session, UserInfo, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
- * @aplons/auth — Anmeldung über Aplons in eigenen Anwendungen.
2
+ * @aplons/auth — sign in through Aplons from your own applications.
3
3
  *
4
- * Der Kern, ohne Rahmenwerk: läuft in Node, im Browser, am Rand und in einem
5
- * Worker. Für Next.js gibt es zusätzlich `@aplons/auth/next`, das die
6
- * Cookie-Arbeit abnimmt.
4
+ * The core, framework-free: runs in Node, in the browser, at the edge and in
5
+ * a worker. For Next.js there is also `@aplons/auth/next`, which takes the
6
+ * cookie work off your hands.
7
7
  *
8
- * Eine einzige Abhängigkeit, `jose`, und die nur zum Prüfen von Unterschriften
9
- * dieselbe, die der Aplons-Server selbst benutzt. Alles andere macht die
10
- * eingebaute Web-Crypto.
8
+ * A single dependency, `jose`, and only for verifying signatures the same
9
+ * one the Aplons server itself uses. Everything else is built-in Web Crypto.
11
10
  */
12
11
  export { AplonsAuth } from "./client.js";
13
12
  export { AplonsError } from "./errors.js";
14
- export { base64url, createChallenge, createState, createVerifier, gleich, } from "./pkce.js";
15
- export { pruefeIdToken, pruefeZugriffstoken, schluesselFuer } from "./verify.js";
16
- export { holeMetadaten } from "./discovery.js";
13
+ export { base64url, createChallenge, createState, createVerifier, timingSafeEqual, } from "./pkce.js";
14
+ export { jwksFor, verifyAccessToken, verifyIdToken } from "./verify.js";
15
+ export { brandingCss } from "./branding.js";
16
+ export { fetchMetadata } from "./discovery.js";
package/dist/next.d.ts CHANGED
@@ -1,72 +1,74 @@
1
1
  /**
2
- * @aplons/auth/next — der Teil, den man sonst jedes Mal neu schreibt.
2
+ * @aplons/auth/next — the part you would otherwise write from scratch every
3
+ * time.
3
4
  *
4
- * Der Kern kennt keine Cookies; er weiß nichts davon, wo `verifier` und
5
- * `state` zwischen zwei Aufrufen liegen. Genau dort steckt aber die Arbeit,
6
- * und genau dort werden die Fehler gemacht: der Verifier im localStorage
7
- * (jedes Skript auf der Seite liest ihn), der State ohne `httpOnly`, ein
8
- * Cookie ohne `secure`, das über einen offenen Hotspot geht.
5
+ * The core knows nothing about cookies; it has no idea where `verifier` and
6
+ * `state` live between two requests. But that is exactly where the work is,
7
+ * and exactly where the mistakes get made: the verifier in localStorage
8
+ * (every script on the page reads it), the state without `httpOnly`, a
9
+ * cookie without `secure` travelling over an open hotspot.
9
10
  *
10
- * Hier passiert das einmal richtig:
11
+ * Here that happens once, correctly:
11
12
  *
12
13
  * app/api/auth/[...aplons]/route.ts
13
14
  * ------------------------------------------------------------------
14
- * import { handhabe } from "@aplons/auth/next";
15
+ * import { createHandler } from "@aplons/auth/next";
15
16
  *
16
- * export const { GET, POST } = handhabe({
17
+ * export const { GET, POST } = createHandler({
17
18
  * issuer: process.env.APLONS_ISSUER!,
18
19
  * clientId: process.env.APLONS_CLIENT_ID!,
19
20
  * clientSecret: process.env.APLONS_CLIENT_SECRET,
20
21
  * redirectUri: process.env.APLONS_REDIRECT_URI!,
21
22
  * });
22
23
  *
23
- * Das ergibt vier Adressen: /api/auth/login, /callback, /logout, /me.
24
+ * That yields four routes: /api/auth/login, /callback, /logout, /me.
24
25
  *
25
- * Next.js ist eine Peer-Abhängigkeit und wird hier absichtlich nicht
26
- * importiert dieses Modul kommt mit `Request` und `Response` aus, und die
27
- * gibt es überall. So lässt es sich auch woanders verwenden, und wer nur den
28
- * Kern will, zieht sich Next nicht mit ein.
26
+ * Next.js is a peer dependency and is deliberately not imported here — this
27
+ * module gets by with `Request` and `Response`, which exist everywhere. That
28
+ * makes it usable elsewhere too, and whoever only wants the core does not
29
+ * pull Next in with it.
29
30
  */
30
31
  import { AplonsAuth } from "./client.js";
31
- import type { AplonsOptions, Sitzung } from "./types.js";
32
+ import type { Branding } from "./branding.js";
33
+ import type { AplonsOptions, Session } from "./types.js";
32
34
  export type HandlerOptions = AplonsOptions & {
33
35
  /**
34
- * Präfix, unter dem die vier Adressen liegen.
35
- * Voreinstellung: aus `redirectUri` abgeleitet.
36
+ * Prefix the four routes live under.
37
+ * Defaults to whatever `redirectUri` implies.
36
38
  */
37
39
  basePath?: string;
38
- /** Wohin nach der Anmeldung, wenn nichts anderes verlangt wurde. */
39
- nachAnmeldung?: string;
40
- /** Wohin nach dem Abmelden. */
41
- nachAbmeldung?: string;
40
+ /** Where to go after login when nothing else was requested. */
41
+ afterLogin?: string;
42
+ /** Where to go after logout. */
43
+ afterLogout?: string;
42
44
  /**
43
- * Name des Cookies mit der Sitzung.
44
- * Voreinstellung `aplons_session`.
45
+ * Name of the session cookie.
46
+ * Defaults to `aplons_session`.
45
47
  */
46
48
  cookieName?: string;
47
49
  /**
48
- * Wie die Sitzung abgelegt wird.
50
+ * How the session is stored.
49
51
  *
50
- * Ohne eigene Angabe landet sie **verschlüsselt im Cookie**. Das kommt
51
- * ohne Speicher aus, hat aber eine Grenze: 4 KB, und ein Widerruf wirkt
52
- * erst, wenn das Zugriffstoken abläuft. Wer eine Datenbank hat, gibt hier
53
- * seine eigene Ablage an.
52
+ * Without an explicit choice it ends up **in the cookie**. That needs no
53
+ * storage, but it has limits: 4 KB, and a revocation only bites once the
54
+ * access token expires. Whoever has a database passes their own store.
54
55
  */
55
- speicher?: Sitzungsspeicher;
56
+ store?: SessionStore;
56
57
  };
57
- export type Sitzungsspeicher = {
58
- lies(id: string): Promise<Sitzung | null>;
59
- schreib(id: string, sitzung: Sitzung): Promise<void>;
60
- loesche(id: string): Promise<void>;
58
+ export type SessionStore = {
59
+ read(id: string): Promise<Session | null>;
60
+ write(id: string, session: Session): Promise<void>;
61
+ delete(id: string): Promise<void>;
61
62
  };
62
63
  /**
63
- * Die vier Adressen.
64
+ * The four routes.
64
65
  *
65
- * Zurückgegeben als `{ GET, POST }`, weil der App Router genau das erwartet.
66
+ * Returned as `{ GET, POST }`, because that is what the App Router expects.
66
67
  */
67
- export declare function handhabe(options: HandlerOptions): {
68
+ export declare function createHandler(options: HandlerOptions): {
68
69
  GET: (request: Request) => Promise<Response>;
69
70
  POST: (request: Request) => Promise<Response>;
70
71
  auth: AplonsAuth;
71
- sitzungAus: (request: Request) => Promise<Sitzung | null>;
72
+ getSession: (request: Request) => Promise<Session | null>;
73
+ getBranding: (request: Request) => Promise<Branding | null>;
72
74
  };