@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 +88 -28
- package/dist/branding.d.ts +82 -0
- package/dist/branding.js +80 -0
- package/dist/client.d.ts +78 -55
- package/dist/client.js +173 -124
- package/dist/discovery.d.ts +11 -9
- package/dist/discovery.js +26 -26
- package/dist/errors.d.ts +12 -12
- package/dist/errors.js +49 -45
- package/dist/index.d.ts +12 -12
- package/dist/index.js +10 -10
- package/dist/next.d.ts +39 -37
- package/dist/next.js +259 -159
- package/dist/pkce.d.ts +27 -29
- package/dist/pkce.js +41 -43
- package/dist/types.d.ts +31 -32
- package/dist/verify.d.ts +14 -14
- package/dist/verify.js +47 -47
- package/package.json +8 -6
package/dist/errors.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* An error that says what to do about it.
|
|
3
3
|
*
|
|
4
|
-
* OAuth
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
/**
|
|
10
|
+
/** The OAuth identifier, e.g. `invalid_grant`. */
|
|
11
11
|
code;
|
|
12
|
-
/**
|
|
12
|
+
/** The HTTP status, when the error came from a response. */
|
|
13
13
|
status;
|
|
14
|
-
/**
|
|
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`
|
|
25
|
+
* `invalid_grant` means something different in every flow.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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: "
|
|
33
|
-
"code_verifier
|
|
34
|
-
"
|
|
35
|
-
refresh: "
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
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 exchanged — every 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
|
-
/**
|
|
43
|
-
const
|
|
44
|
-
invalid_client: "Client
|
|
45
|
-
"
|
|
46
|
-
invalid_request: "
|
|
47
|
-
unauthorized_client: "
|
|
48
|
-
access_denied: "
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
/**
|
|
54
|
-
export async function
|
|
55
|
-
let code = "http_" +
|
|
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
|
|
59
|
-
if (
|
|
60
|
-
code =
|
|
61
|
-
description =
|
|
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
|
-
//
|
|
65
|
-
//
|
|
68
|
+
// Not a JSON response. Then the status is all there is — which happens
|
|
69
|
+
// more often with a proxy in between than with us.
|
|
66
70
|
}
|
|
67
|
-
const
|
|
71
|
+
const explanation = code === "invalid_grant" ? INVALID_GRANT[flow] : EXPLANATIONS[code];
|
|
68
72
|
return new AplonsError({
|
|
69
73
|
code,
|
|
70
|
-
status:
|
|
74
|
+
status: response.status,
|
|
71
75
|
description,
|
|
72
76
|
message: [
|
|
73
|
-
`${
|
|
74
|
-
|
|
75
|
-
description && description !==
|
|
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 —
|
|
2
|
+
* @aplons/auth — sign in through Aplons from your own applications.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
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 —
|
|
2
|
+
* @aplons/auth — sign in through Aplons from your own applications.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
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 —
|
|
2
|
+
* @aplons/auth/next — the part you would otherwise write from scratch every
|
|
3
|
+
* time.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `state`
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
+
* Here that happens once, correctly:
|
|
11
12
|
*
|
|
12
13
|
* app/api/auth/[...aplons]/route.ts
|
|
13
14
|
* ------------------------------------------------------------------
|
|
14
|
-
* import {
|
|
15
|
+
* import { createHandler } from "@aplons/auth/next";
|
|
15
16
|
*
|
|
16
|
-
* export const { GET, POST } =
|
|
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
|
-
*
|
|
24
|
+
* That yields four routes: /api/auth/login, /callback, /logout, /me.
|
|
24
25
|
*
|
|
25
|
-
* Next.js
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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 {
|
|
32
|
+
import type { Branding } from "./branding.js";
|
|
33
|
+
import type { AplonsOptions, Session } from "./types.js";
|
|
32
34
|
export type HandlerOptions = AplonsOptions & {
|
|
33
35
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Prefix the four routes live under.
|
|
37
|
+
* Defaults to whatever `redirectUri` implies.
|
|
36
38
|
*/
|
|
37
39
|
basePath?: string;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
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
|
|
44
|
-
*
|
|
45
|
+
* Name of the session cookie.
|
|
46
|
+
* Defaults to `aplons_session`.
|
|
45
47
|
*/
|
|
46
48
|
cookieName?: string;
|
|
47
49
|
/**
|
|
48
|
-
*
|
|
50
|
+
* How the session is stored.
|
|
49
51
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
-
|
|
56
|
+
store?: SessionStore;
|
|
56
57
|
};
|
|
57
|
-
export type
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
*
|
|
64
|
+
* The four routes.
|
|
64
65
|
*
|
|
65
|
-
*
|
|
66
|
+
* Returned as `{ GET, POST }`, because that is what the App Router expects.
|
|
66
67
|
*/
|
|
67
|
-
export declare function
|
|
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
|
-
|
|
72
|
+
getSession: (request: Request) => Promise<Session | null>;
|
|
73
|
+
getBranding: (request: Request) => Promise<Branding | null>;
|
|
72
74
|
};
|