imapkit 5.2.0 → 5.3.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 +37 -21
- package/bin/help.txt +25 -1
- package/bin/imapkit.js +35 -0
- package/dist/browser/imapkit.js +99 -35
- package/dist/cjs/control.d.ts +16 -5
- package/dist/cjs/control.js +35 -4
- package/dist/cjs/oauth/http.d.ts +28 -0
- package/dist/cjs/oauth/http.js +216 -0
- package/dist/cjs/oauth/jwt.d.ts +33 -0
- package/dist/cjs/oauth/jwt.js +58 -0
- package/dist/cjs/oauth/pages.d.ts +47 -0
- package/dist/cjs/oauth/pages.js +310 -0
- package/dist/cjs/oauth/policy.d.ts +48 -0
- package/dist/cjs/oauth/policy.js +66 -0
- package/dist/cjs/oauth/provider.d.ts +455 -0
- package/dist/cjs/oauth/provider.js +1422 -0
- package/dist/cjs/plugins/oauthbearer.js +10 -15
- package/dist/cjs/plugins/xoauth2.js +4 -3
- package/dist/cjs/rest.d.ts +18 -1
- package/dist/cjs/rest.js +49 -31
- package/dist/cjs/server.d.ts +25 -1
- package/dist/cjs/server.js +73 -11
- package/dist/cjs/smtp-listener.d.ts +3 -2
- package/dist/cjs/smtp-listener.js +15 -3
- package/dist/cjs/types.d.ts +10 -0
- package/dist/esm/control.d.ts +16 -5
- package/dist/esm/control.js +35 -5
- package/dist/esm/oauth/http.d.ts +28 -0
- package/dist/esm/oauth/http.js +211 -0
- package/dist/esm/oauth/jwt.d.ts +33 -0
- package/dist/esm/oauth/jwt.js +50 -0
- package/dist/esm/oauth/pages.d.ts +47 -0
- package/dist/esm/oauth/pages.js +305 -0
- package/dist/esm/oauth/policy.d.ts +48 -0
- package/dist/esm/oauth/policy.js +59 -0
- package/dist/esm/oauth/provider.d.ts +455 -0
- package/dist/esm/oauth/provider.js +1415 -0
- package/dist/esm/plugins/oauthbearer.js +10 -15
- package/dist/esm/plugins/xoauth2.js +4 -3
- package/dist/esm/rest.d.ts +18 -1
- package/dist/esm/rest.js +48 -32
- package/dist/esm/server.d.ts +25 -1
- package/dist/esm/server.js +74 -12
- package/dist/esm/smtp-listener.d.ts +3 -2
- package/dist/esm/smtp-listener.js +15 -3
- package/dist/esm/types.d.ts +10 -0
- package/dist/plugin-help.json +2 -2
- package/package.json +6 -6
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP server of the OAuth provider (`oauth` option, `--oauth-port`), on node:http or node:https only.
|
|
3
|
+
* Routes: `/` (index), `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server`
|
|
4
|
+
* (discovery), `/authorize` (GET and POST, OpenID Connect Core 1.0 section 3.1.2.1), `/authorize/decision` (the
|
|
5
|
+
* consent page form), `/token`, `/revoke`, `/userinfo` and `/jwks`.
|
|
6
|
+
*
|
|
7
|
+
* Bound to a loopback address (the default), it only answers requests for a loopback host name or the host of the
|
|
8
|
+
* `issuer` option, so a web page can not rebind its DNS name to it. The JSON endpoints allow cross-origin requests,
|
|
9
|
+
* like real providers do for browser-based clients; the pages are never framed and run no script.
|
|
10
|
+
*/
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import type { OAuthProvider } from './provider.js';
|
|
13
|
+
/**
|
|
14
|
+
* Creates the HTTP server of the provider, it does not listen yet
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} provider OAuth provider
|
|
17
|
+
* @param {Object} options `{ secure, host, credentials }`, credentials are the TLS key and certificate of `secure`
|
|
18
|
+
* @return {Object} HTTP or HTTPS server
|
|
19
|
+
*/
|
|
20
|
+
declare function createOAuthServer(provider: OAuthProvider, options: {
|
|
21
|
+
secure?: boolean | undefined;
|
|
22
|
+
host: string;
|
|
23
|
+
credentials: {
|
|
24
|
+
key: string | Buffer;
|
|
25
|
+
cert: string | Buffer;
|
|
26
|
+
};
|
|
27
|
+
}): http.Server;
|
|
28
|
+
export { createOAuthServer };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP server of the OAuth provider (`oauth` option, `--oauth-port`), on node:http or node:https only.
|
|
3
|
+
* Routes: `/` (index), `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server`
|
|
4
|
+
* (discovery), `/authorize` (GET and POST, OpenID Connect Core 1.0 section 3.1.2.1), `/authorize/decision` (the
|
|
5
|
+
* consent page form), `/token`, `/revoke`, `/userinfo` and `/jwks`.
|
|
6
|
+
*
|
|
7
|
+
* Bound to a loopback address (the default), it only answers requests for a loopback host name or the host of the
|
|
8
|
+
* `issuer` option, so a web page can not rebind its DNS name to it. The JSON endpoints allow cross-origin requests,
|
|
9
|
+
* like real providers do for browser-based clients; the pages are never framed and run no script.
|
|
10
|
+
*/
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import https from 'node:https';
|
|
13
|
+
import { consentPage, indexPage, messagePage } from './pages.js';
|
|
14
|
+
import { isLoopback, readRaw } from '../rest.js';
|
|
15
|
+
// a form of a mail client sign-in is small, larger bodies are refused
|
|
16
|
+
const BODY_LIMIT = 64 * 1024;
|
|
17
|
+
class HttpError extends Error {
|
|
18
|
+
constructor(status, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.status = status;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads an application/x-www-form-urlencoded body
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} req HTTP request
|
|
27
|
+
* @return {Promise<URLSearchParams>} the form
|
|
28
|
+
*/
|
|
29
|
+
async function readForm(req) {
|
|
30
|
+
// RFC 6749 sections 3.2 and 4.1.3: the parameters are form encoded
|
|
31
|
+
if (!/^application\/x-www-form-urlencoded\b/i.test(req.headers['content-type'] || '')) {
|
|
32
|
+
req.resume();
|
|
33
|
+
throw new HttpError(415, 'The body must be application/x-www-form-urlencoded (RFC 6749 section 4.1.3)');
|
|
34
|
+
}
|
|
35
|
+
const body = await readRaw(req, BODY_LIMIT, () => new HttpError(413, 'The body is larger than 64 KiB'));
|
|
36
|
+
return new URLSearchParams(body.toString('utf-8'));
|
|
37
|
+
}
|
|
38
|
+
const SECURITY_HEADERS = {
|
|
39
|
+
'Cache-Control': 'no-store',
|
|
40
|
+
Pragma: 'no-cache',
|
|
41
|
+
'X-Content-Type-Options': 'nosniff',
|
|
42
|
+
'Referrer-Policy': 'no-referrer'
|
|
43
|
+
};
|
|
44
|
+
const CORS_HEADERS = {
|
|
45
|
+
'Access-Control-Allow-Origin': '*',
|
|
46
|
+
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
|
|
47
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS'
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The form-action sources of the consent page. Browsers check form-action on the redirect after the form too,
|
|
51
|
+
* so the origin of the client's redirect URI must be allowed
|
|
52
|
+
*
|
|
53
|
+
* @param {Object} request Pending request
|
|
54
|
+
* @return {String} CSP source list
|
|
55
|
+
*/
|
|
56
|
+
function formActionSources(request) {
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(request.redirectUri);
|
|
59
|
+
return "'self' " + (url.origin !== 'null' ? url.origin : url.protocol);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return "'self'";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function sendHtml(res, status, html, formAction = "'none'") {
|
|
66
|
+
res.writeHead(status, {
|
|
67
|
+
...SECURITY_HEADERS,
|
|
68
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
69
|
+
'Content-Length': Buffer.byteLength(html),
|
|
70
|
+
'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'; form-action " + formAction,
|
|
71
|
+
'X-Frame-Options': 'DENY'
|
|
72
|
+
});
|
|
73
|
+
res.end(html);
|
|
74
|
+
}
|
|
75
|
+
function sendJson(res, result) {
|
|
76
|
+
if (result.htmlError) {
|
|
77
|
+
// like a provider behind a broken proxy
|
|
78
|
+
sendHtml(res, result.status, '<!doctype html><title>Error</title><h1>Something went wrong</h1>');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const json = JSON.stringify(result.body);
|
|
82
|
+
res.writeHead(result.status, {
|
|
83
|
+
...SECURITY_HEADERS,
|
|
84
|
+
...CORS_HEADERS,
|
|
85
|
+
...(result.headers || {}),
|
|
86
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
87
|
+
'Content-Length': Buffer.byteLength(json)
|
|
88
|
+
});
|
|
89
|
+
res.end(json);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Checks the Origin of the consent form: the issuer, or the loopback name the page was opened on. A browser that
|
|
93
|
+
* sends no Origin, or "null" (Referrer-Policy: no-referrer), is let through; the request ID protects the form then
|
|
94
|
+
*
|
|
95
|
+
* @param {String} [origin] Origin header
|
|
96
|
+
* @param {String} issuer Issuer URL
|
|
97
|
+
* @param {String} [host] Host header
|
|
98
|
+
* @return {Boolean} true if the form may come from there
|
|
99
|
+
*/
|
|
100
|
+
function sameSite(origin, issuer, host) {
|
|
101
|
+
if (!origin || origin === 'null' || origin === new URL(issuer).origin) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const url = new URL(origin);
|
|
106
|
+
return isLoopback(url.hostname) && url.host === host;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates the HTTP server of the provider, it does not listen yet
|
|
114
|
+
*
|
|
115
|
+
* @param {Object} provider OAuth provider
|
|
116
|
+
* @param {Object} options `{ secure, host, credentials }`, credentials are the TLS key and certificate of `secure`
|
|
117
|
+
* @return {Object} HTTP or HTTPS server
|
|
118
|
+
*/
|
|
119
|
+
function createOAuthServer(provider, options) {
|
|
120
|
+
const answerAuthorize = (res, result, status) => {
|
|
121
|
+
if (result.type === 'redirect') {
|
|
122
|
+
res.writeHead(status, { ...SECURITY_HEADERS, Location: result.location, 'Content-Type': 'text/plain; charset=utf-8' });
|
|
123
|
+
res.end('Redirecting to ' + result.location + '\n');
|
|
124
|
+
}
|
|
125
|
+
else if (result.type === 'page') {
|
|
126
|
+
sendHtml(res, result.page.status, messagePage(result.page, provider.issuer));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const html = consentPage({ request: result.request, users: provider.pageUsers(), scopes: provider.scopes, issuer: provider.issuer });
|
|
130
|
+
sendHtml(res, 200, html, formActionSources(result.request));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const checkHost = (req) => {
|
|
134
|
+
if (!isLoopback(options.host)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const host = (req.headers.host || '').replace(/:\d+$/, '');
|
|
138
|
+
let issuerHost = '';
|
|
139
|
+
try {
|
|
140
|
+
issuerHost = new URL(provider.issuer).hostname;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// no issuer yet
|
|
144
|
+
}
|
|
145
|
+
if (!isLoopback(host) && host.replace(/^\[|\]$/g, '') !== issuerHost.replace(/^\[|\]$/g, '')) {
|
|
146
|
+
// a page that rebinds its DNS name to 127.0.0.1 sends its own host name
|
|
147
|
+
throw new HttpError(421, 'Requests must use a loopback host name or the host of the issuer');
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const handle = async (req, res) => {
|
|
151
|
+
checkHost(req);
|
|
152
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
153
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
154
|
+
const route = method + ' ' + url.pathname;
|
|
155
|
+
if (method === 'OPTIONS') {
|
|
156
|
+
res.writeHead(204, { ...CORS_HEADERS, 'Access-Control-Max-Age': '600' });
|
|
157
|
+
return res.end();
|
|
158
|
+
}
|
|
159
|
+
switch (route) {
|
|
160
|
+
case 'GET /':
|
|
161
|
+
return sendHtml(res, 200, indexPage({
|
|
162
|
+
issuer: provider.issuer,
|
|
163
|
+
consent: provider.consent,
|
|
164
|
+
clients: [...provider.clients.values()],
|
|
165
|
+
users: provider.pageUsers(),
|
|
166
|
+
mailScope: provider.mailScope
|
|
167
|
+
}));
|
|
168
|
+
case 'GET /.well-known/openid-configuration':
|
|
169
|
+
case 'GET /.well-known/oauth-authorization-server':
|
|
170
|
+
return sendJson(res, { status: 200, body: provider.metadata() });
|
|
171
|
+
case 'GET /jwks':
|
|
172
|
+
return sendJson(res, { status: 200, body: provider.jwks() });
|
|
173
|
+
case 'GET /authorize':
|
|
174
|
+
return answerAuthorize(res, provider.authorize(url.searchParams), 302);
|
|
175
|
+
case 'POST /authorize':
|
|
176
|
+
// RFC 9700 section 4.12: 303 turns the POST into a GET, never 307
|
|
177
|
+
return answerAuthorize(res, provider.authorize(await readForm(req)), 303);
|
|
178
|
+
case 'POST /authorize/decision': {
|
|
179
|
+
if (!sameSite(req.headers.origin, provider.issuer, req.headers.host)) {
|
|
180
|
+
throw new HttpError(403, 'The consent form must come from the consent page');
|
|
181
|
+
}
|
|
182
|
+
return answerAuthorize(res, provider.answerConsent(await readForm(req)), 303);
|
|
183
|
+
}
|
|
184
|
+
case 'POST /token':
|
|
185
|
+
return sendJson(res, await provider.token(await readForm(req), req.headers.authorization));
|
|
186
|
+
case 'POST /revoke':
|
|
187
|
+
return sendJson(res, provider.revoke(await readForm(req), req.headers.authorization));
|
|
188
|
+
case 'GET /userinfo':
|
|
189
|
+
return sendJson(res, provider.userinfo(req.headers.authorization));
|
|
190
|
+
case 'POST /userinfo':
|
|
191
|
+
req.resume();
|
|
192
|
+
return sendJson(res, provider.userinfo(req.headers.authorization));
|
|
193
|
+
default: {
|
|
194
|
+
const known = ['/', '/authorize', '/authorize/decision', '/token', '/revoke', '/userinfo', '/jwks'];
|
|
195
|
+
const wellKnown = url.pathname.startsWith('/.well-known/');
|
|
196
|
+
throw new HttpError(known.includes(url.pathname) || wellKnown ? 405 : 404, known.includes(url.pathname) ? 'Method not allowed' : 'Not found');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const listener = (req, res) => {
|
|
201
|
+
handle(req, res).catch((err) => {
|
|
202
|
+
const status = err instanceof HttpError ? err.status : 500;
|
|
203
|
+
if (!res.headersSent) {
|
|
204
|
+
// the token and revocation endpoints answer errors in JSON (RFC 6749 section 5.2)
|
|
205
|
+
sendJson(res, { status, body: { error: status === 500 ? 'server_error' : 'invalid_request', error_description: err.message } });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
return options.secure ? https.createServer(options.credentials, listener) : http.createServer(listener);
|
|
210
|
+
}
|
|
211
|
+
export { createOAuthServer };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs the ID tokens of the OAuth provider: RS256 JSON Web Tokens (RFC 7519, RFC 7515) and the JWK Set of the
|
|
3
|
+
* public key (RFC 7517), with the RFC 7638 thumbprint as the key ID
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
/** An RSA signing key with its public JWK */
|
|
7
|
+
export interface SigningKey {
|
|
8
|
+
privateKey: crypto.KeyObject;
|
|
9
|
+
jwk: Record<string, string>;
|
|
10
|
+
kid: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Loads or makes the signing key
|
|
14
|
+
*
|
|
15
|
+
* @param {String|Buffer} [pem] PKCS#8 or PKCS#1 private key in PEM, a new 2048 bit key if not set (see defaultSigningKey())
|
|
16
|
+
* @return {Object} the key, its public JWK and key ID
|
|
17
|
+
* @throws {Error} for a key that is not RSA
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadSigningKey(pem?: string | Buffer): SigningKey;
|
|
20
|
+
/**
|
|
21
|
+
* The shared signing key of the providers without the signingKey option, made when the first one needs it
|
|
22
|
+
*
|
|
23
|
+
* @return {Object} signing key
|
|
24
|
+
*/
|
|
25
|
+
export declare function defaultSigningKey(): SigningKey;
|
|
26
|
+
/**
|
|
27
|
+
* Signs a JWT with RS256
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} key Signing key
|
|
30
|
+
* @param {Object} claims JWT claims
|
|
31
|
+
* @return {String} compact serialization
|
|
32
|
+
*/
|
|
33
|
+
export declare function signJwt(key: SigningKey, claims: Record<string, unknown>): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs the ID tokens of the OAuth provider: RS256 JSON Web Tokens (RFC 7519, RFC 7515) and the JWK Set of the
|
|
3
|
+
* public key (RFC 7517), with the RFC 7638 thumbprint as the key ID
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
const base64url = (data) => Buffer.from(data).toString('base64url');
|
|
7
|
+
/**
|
|
8
|
+
* Loads or makes the signing key
|
|
9
|
+
*
|
|
10
|
+
* @param {String|Buffer} [pem] PKCS#8 or PKCS#1 private key in PEM, a new 2048 bit key if not set (see defaultSigningKey())
|
|
11
|
+
* @return {Object} the key, its public JWK and key ID
|
|
12
|
+
* @throws {Error} for a key that is not RSA
|
|
13
|
+
*/
|
|
14
|
+
export function loadSigningKey(pem) {
|
|
15
|
+
const privateKey = pem ? crypto.createPrivateKey(pem) : crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }).privateKey;
|
|
16
|
+
if (privateKey.asymmetricKeyType !== 'rsa') {
|
|
17
|
+
throw new Error('oauth.signingKey must be an RSA private key, ID tokens are signed with RS256');
|
|
18
|
+
}
|
|
19
|
+
const { n, e } = crypto.createPublicKey(privateKey).export({ format: 'jwk' });
|
|
20
|
+
// RFC 7638 section 3.2: the required members in lexicographic order, no white space
|
|
21
|
+
const kid = base64url(crypto
|
|
22
|
+
.createHash('sha256')
|
|
23
|
+
.update(JSON.stringify({ e, kty: 'RSA', n }))
|
|
24
|
+
.digest());
|
|
25
|
+
return { privateKey, kid, jwk: { kty: 'RSA', use: 'sig', alg: 'RS256', kid, n, e } };
|
|
26
|
+
}
|
|
27
|
+
// the key of every provider without a signingKey option, made once: a 2048 bit RSA key takes a moment
|
|
28
|
+
let defaultKey = null;
|
|
29
|
+
/**
|
|
30
|
+
* The shared signing key of the providers without the signingKey option, made when the first one needs it
|
|
31
|
+
*
|
|
32
|
+
* @return {Object} signing key
|
|
33
|
+
*/
|
|
34
|
+
export function defaultSigningKey() {
|
|
35
|
+
if (!defaultKey) {
|
|
36
|
+
defaultKey = loadSigningKey();
|
|
37
|
+
}
|
|
38
|
+
return defaultKey;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Signs a JWT with RS256
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} key Signing key
|
|
44
|
+
* @param {Object} claims JWT claims
|
|
45
|
+
* @return {String} compact serialization
|
|
46
|
+
*/
|
|
47
|
+
export function signJwt(key, claims) {
|
|
48
|
+
const input = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: key.kid })) + '.' + base64url(JSON.stringify(claims));
|
|
49
|
+
return input + '.' + base64url(crypto.sign('sha256', Buffer.from(input), key.privateKey));
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pages of the OAuth provider: the consent page, error and info pages, and the index page at `/`. They are a
|
|
3
|
+
* test tool's pages and say so (a striped "test server" ribbon, ImapKit's own mark, no password field), so that
|
|
4
|
+
* nobody takes them for a real provider's sign-in. They run no script and load nothing, src/oauth/http.ts sends
|
|
5
|
+
* them with a strict Content-Security-Policy.
|
|
6
|
+
*/
|
|
7
|
+
import type { OAuthClient, PageData, PendingRequest } from './provider.js';
|
|
8
|
+
import type { ConsentPolicy } from './policy.js';
|
|
9
|
+
/** A user on the consent page */
|
|
10
|
+
export interface PageUser {
|
|
11
|
+
name: string;
|
|
12
|
+
displayName: string | undefined;
|
|
13
|
+
consent: ConsentPolicy | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The consent page: pick an account, Allow or Deny
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} data `{ request, users, scopes, issuer }`
|
|
19
|
+
* @return {String} HTML
|
|
20
|
+
*/
|
|
21
|
+
export declare function consentPage(data: {
|
|
22
|
+
request: PendingRequest;
|
|
23
|
+
users: PageUser[];
|
|
24
|
+
scopes: Record<string, string>;
|
|
25
|
+
issuer: string;
|
|
26
|
+
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* An error or info page, instead of a redirect
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} page Page data
|
|
31
|
+
* @param {String} issuer Issuer URL
|
|
32
|
+
* @return {String} HTML
|
|
33
|
+
*/
|
|
34
|
+
export declare function messagePage(page: PageData, issuer: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The index page at `/`: issuer, clients and endpoints
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} data `{ issuer, consent, clients, users, mailScope }`
|
|
39
|
+
* @return {String} HTML
|
|
40
|
+
*/
|
|
41
|
+
export declare function indexPage(data: {
|
|
42
|
+
issuer: string;
|
|
43
|
+
consent: ConsentPolicy;
|
|
44
|
+
clients: OAuthClient[];
|
|
45
|
+
users: PageUser[];
|
|
46
|
+
mailScope: string;
|
|
47
|
+
}): string;
|