@draftlab/auth 0.0.1
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/adapters/node.d.ts +18 -0
- package/dist/adapters/node.js +71 -0
- package/dist/allow-CixonwTW.d.ts +59 -0
- package/dist/allow-DX5cehSc.js +63 -0
- package/dist/allow.d.ts +2 -0
- package/dist/allow.js +4 -0
- package/dist/base-DRutbxgL.js +422 -0
- package/dist/client.d.ts +413 -0
- package/dist/client.js +209 -0
- package/dist/code-l_uvMR1j.d.ts +212 -0
- package/dist/core-8WTqfnb4.d.ts +129 -0
- package/dist/core-CncE5rPg.js +498 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +14 -0
- package/dist/error-CWAdNAzm.d.ts +243 -0
- package/dist/error-DgAKK7b2.js +237 -0
- package/dist/error.d.ts +2 -0
- package/dist/error.js +3 -0
- package/dist/form-6XKM_cOk.js +61 -0
- package/dist/icon-Ci5uqGB_.js +192 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +14 -0
- package/dist/keys-EEfxEGfO.js +140 -0
- package/dist/keys.d.ts +67 -0
- package/dist/keys.js +5 -0
- package/dist/oauth2-B7-6Z7Lc.js +155 -0
- package/dist/oauth2-DtKwtl8p.d.ts +176 -0
- package/dist/password-Cm0dRMwa.d.ts +385 -0
- package/dist/pkce-276Za_rZ.js +162 -0
- package/dist/pkce.d.ts +72 -0
- package/dist/pkce.js +3 -0
- package/dist/provider/code.d.ts +4 -0
- package/dist/provider/code.js +145 -0
- package/dist/provider/facebook.d.ts +137 -0
- package/dist/provider/facebook.js +85 -0
- package/dist/provider/github.d.ts +141 -0
- package/dist/provider/github.js +88 -0
- package/dist/provider/google.d.ts +113 -0
- package/dist/provider/google.js +62 -0
- package/dist/provider/oauth2.d.ts +4 -0
- package/dist/provider/oauth2.js +7 -0
- package/dist/provider/password.d.ts +4 -0
- package/dist/provider/password.js +366 -0
- package/dist/provider/provider.d.ts +3 -0
- package/dist/provider/provider.js +44 -0
- package/dist/provider-CwWMG-1l.d.ts +227 -0
- package/dist/random-SXMYlaVr.js +87 -0
- package/dist/random.d.ts +66 -0
- package/dist/random.js +3 -0
- package/dist/select-BjySLL8I.js +280 -0
- package/dist/storage/memory.d.ts +82 -0
- package/dist/storage/memory.js +127 -0
- package/dist/storage/storage.d.ts +2 -0
- package/dist/storage/storage.js +3 -0
- package/dist/storage/turso.d.ts +31 -0
- package/dist/storage/turso.js +117 -0
- package/dist/storage/unstorage.d.ts +38 -0
- package/dist/storage/unstorage.js +97 -0
- package/dist/storage-BEaqEPNQ.js +62 -0
- package/dist/storage-CxKerLlc.d.ts +162 -0
- package/dist/subject-DiQdRWGt.d.ts +62 -0
- package/dist/subject.d.ts +3 -0
- package/dist/subject.js +36 -0
- package/dist/theme-C9by7VXf.d.ts +209 -0
- package/dist/theme-CswaLtbW.js +120 -0
- package/dist/themes/theme.d.ts +2 -0
- package/dist/themes/theme.js +3 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +0 -0
- package/dist/ui/base.d.ts +43 -0
- package/dist/ui/base.js +4 -0
- package/dist/ui/code.d.ts +158 -0
- package/dist/ui/code.js +197 -0
- package/dist/ui/form.d.ts +31 -0
- package/dist/ui/form.js +3 -0
- package/dist/ui/icon.d.ts +98 -0
- package/dist/ui/icon.js +3 -0
- package/dist/ui/password.d.ts +54 -0
- package/dist/ui/password.js +300 -0
- package/dist/ui/select.d.ts +233 -0
- package/dist/ui/select.js +6 -0
- package/dist/util-CSdHUFOo.js +108 -0
- package/dist/util-ChlgVqPN.d.ts +72 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.js +3 -0
- package/package.json +63 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/node.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts Node.js IncomingMessage to Web Standards Request
|
|
7
|
+
*/
|
|
8
|
+
declare const nodeRequestAdapter: (req: IncomingMessage) => Request;
|
|
9
|
+
/**
|
|
10
|
+
* Writes Web Standards Response to Node.js ServerResponse
|
|
11
|
+
*/
|
|
12
|
+
declare const nodeResponseAdapter: (response: Response, res: ServerResponse) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a Node.js HTTP handler from a Web Standards fetch function
|
|
15
|
+
*/
|
|
16
|
+
declare const createNodeHandler: (fetchHandler: (request: Request) => Promise<Response>) => ((req: IncomingMessage, res: ServerResponse) => void);
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createNodeHandler, nodeRequestAdapter, nodeResponseAdapter };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/node.ts
|
|
4
|
+
/**
|
|
5
|
+
* Converts Node.js IncomingMessage to Web Standards Request
|
|
6
|
+
*/
|
|
7
|
+
const nodeRequestAdapter = (req) => {
|
|
8
|
+
const host = req.headers.host || "localhost";
|
|
9
|
+
const sanitizedHost = host.split(",")[0]?.trim();
|
|
10
|
+
const url = new URL(req.url || "/", `http://${sanitizedHost}`);
|
|
11
|
+
const headers = new Headers();
|
|
12
|
+
for (const [key, value] of Object.entries(req.headers)) if (value !== void 0) if (Array.isArray(value)) for (const v of value) headers.append(key, v);
|
|
13
|
+
else headers.set(key, value);
|
|
14
|
+
let body;
|
|
15
|
+
if (req.method !== "GET" && req.method !== "HEAD") body = Readable.toWeb(req);
|
|
16
|
+
return new Request(url.toString(), {
|
|
17
|
+
method: req.method || "GET",
|
|
18
|
+
headers,
|
|
19
|
+
body,
|
|
20
|
+
duplex: "half"
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Writes Web Standards Response to Node.js ServerResponse
|
|
25
|
+
*/
|
|
26
|
+
const nodeResponseAdapter = async (response, res) => {
|
|
27
|
+
res.statusCode = response.status;
|
|
28
|
+
res.statusMessage = response.statusText;
|
|
29
|
+
response.headers.forEach((value, key) => {
|
|
30
|
+
res.setHeader(key, value);
|
|
31
|
+
});
|
|
32
|
+
if (response.body) {
|
|
33
|
+
const reader = response.body.getReader();
|
|
34
|
+
try {
|
|
35
|
+
while (true) {
|
|
36
|
+
const { done, value } = await reader.read();
|
|
37
|
+
if (done) break;
|
|
38
|
+
if (!res.write(value)) await new Promise((resolve) => res.once("drain", resolve));
|
|
39
|
+
}
|
|
40
|
+
} finally {
|
|
41
|
+
reader.releaseLock();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
res.end();
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Creates a Node.js HTTP handler from a Web Standards fetch function
|
|
48
|
+
*/
|
|
49
|
+
const createNodeHandler = (fetchHandler) => {
|
|
50
|
+
return (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const request = nodeRequestAdapter(req);
|
|
53
|
+
fetchHandler(request).then((response) => nodeResponseAdapter(response, res)).catch((error) => {
|
|
54
|
+
console.error("Handler error:", error instanceof Error ? error.message : "Unknown error");
|
|
55
|
+
if (!res.headersSent) {
|
|
56
|
+
res.statusCode = 500;
|
|
57
|
+
res.end("Internal Server Error");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Request adapter error:", error instanceof Error ? error.message : "Unknown error");
|
|
62
|
+
if (!res.headersSent) {
|
|
63
|
+
res.statusCode = 400;
|
|
64
|
+
res.end("Bad Request");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { createNodeHandler, nodeRequestAdapter, nodeResponseAdapter };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/allow.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Client authorization validation utilities.
|
|
4
|
+
* Provides security checks to determine if OAuth authorization requests should be permitted
|
|
5
|
+
* based on redirect URI validation and domain matching policies.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Input parameters for authorization allow checks.
|
|
9
|
+
* Contains all necessary information to validate if a client request should be permitted.
|
|
10
|
+
*/
|
|
11
|
+
interface AllowCheckInput {
|
|
12
|
+
/** The client ID of the application requesting authorization */
|
|
13
|
+
readonly clientID: string;
|
|
14
|
+
/** The redirect URI where the user will be sent after authorization */
|
|
15
|
+
readonly redirectURI: string;
|
|
16
|
+
/** Optional audience parameter for the authorization request */
|
|
17
|
+
readonly audience?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Default authorization check that validates client requests based on redirect URI security.
|
|
21
|
+
*
|
|
22
|
+
* ## Security Policy
|
|
23
|
+
* - **Localhost**: Always allowed (for development)
|
|
24
|
+
* - **Same domain**: Redirect URI must match request origin at TLD+1 level
|
|
25
|
+
* - **Cross-domain**: Rejected for security
|
|
26
|
+
*
|
|
27
|
+
* This prevents unauthorized applications from hijacking authorization codes by using
|
|
28
|
+
* malicious redirect URIs that don't belong to the legitimate client application.
|
|
29
|
+
*
|
|
30
|
+
* @param input - Client request details including ID and redirect URI
|
|
31
|
+
* @param req - The original HTTP request for domain comparison
|
|
32
|
+
* @returns Promise resolving to true if the request should be allowed
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // Allowed: localhost development
|
|
37
|
+
* await defaultAllowCheck({
|
|
38
|
+
* clientID: "dev-app",
|
|
39
|
+
* redirectURI: "http://localhost:3000/callback"
|
|
40
|
+
* }, request) // → true
|
|
41
|
+
*
|
|
42
|
+
* // Allowed: same domain
|
|
43
|
+
* // Request from: https://myapp.com
|
|
44
|
+
* await defaultAllowCheck({
|
|
45
|
+
* clientID: "web-app",
|
|
46
|
+
* redirectURI: "https://auth.myapp.com/callback"
|
|
47
|
+
* }, request) // → true
|
|
48
|
+
*
|
|
49
|
+
* // Rejected: different domain
|
|
50
|
+
* // Request from: https://myapp.com
|
|
51
|
+
* await defaultAllowCheck({
|
|
52
|
+
* clientID: "malicious-app",
|
|
53
|
+
* redirectURI: "https://evil.com/steal-codes"
|
|
54
|
+
* }, request) // → false
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare const defaultAllowCheck: (input: AllowCheckInput, req: Request) => Promise<boolean>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { AllowCheckInput, defaultAllowCheck };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isDomainMatch } from "./util-CSdHUFOo.js";
|
|
2
|
+
|
|
3
|
+
//#region src/allow.ts
|
|
4
|
+
/**
|
|
5
|
+
* Default authorization check that validates client requests based on redirect URI security.
|
|
6
|
+
*
|
|
7
|
+
* ## Security Policy
|
|
8
|
+
* - **Localhost**: Always allowed (for development)
|
|
9
|
+
* - **Same domain**: Redirect URI must match request origin at TLD+1 level
|
|
10
|
+
* - **Cross-domain**: Rejected for security
|
|
11
|
+
*
|
|
12
|
+
* This prevents unauthorized applications from hijacking authorization codes by using
|
|
13
|
+
* malicious redirect URIs that don't belong to the legitimate client application.
|
|
14
|
+
*
|
|
15
|
+
* @param input - Client request details including ID and redirect URI
|
|
16
|
+
* @param req - The original HTTP request for domain comparison
|
|
17
|
+
* @returns Promise resolving to true if the request should be allowed
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Allowed: localhost development
|
|
22
|
+
* await defaultAllowCheck({
|
|
23
|
+
* clientID: "dev-app",
|
|
24
|
+
* redirectURI: "http://localhost:3000/callback"
|
|
25
|
+
* }, request) // → true
|
|
26
|
+
*
|
|
27
|
+
* // Allowed: same domain
|
|
28
|
+
* // Request from: https://myapp.com
|
|
29
|
+
* await defaultAllowCheck({
|
|
30
|
+
* clientID: "web-app",
|
|
31
|
+
* redirectURI: "https://auth.myapp.com/callback"
|
|
32
|
+
* }, request) // → true
|
|
33
|
+
*
|
|
34
|
+
* // Rejected: different domain
|
|
35
|
+
* // Request from: https://myapp.com
|
|
36
|
+
* await defaultAllowCheck({
|
|
37
|
+
* clientID: "malicious-app",
|
|
38
|
+
* redirectURI: "https://evil.com/steal-codes"
|
|
39
|
+
* }, request) // → false
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
const defaultAllowCheck = (input, req) => {
|
|
43
|
+
return Promise.resolve((() => {
|
|
44
|
+
let redirectHostname;
|
|
45
|
+
try {
|
|
46
|
+
redirectHostname = new URL(input.redirectURI).hostname;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (redirectHostname === "localhost" || redirectHostname === "127.0.0.1") return true;
|
|
51
|
+
let currentHostname;
|
|
52
|
+
try {
|
|
53
|
+
const forwardedHost = req.headers.get("x-forwarded-host");
|
|
54
|
+
currentHostname = forwardedHost ? new URL(`https://${forwardedHost}`).hostname : new URL(req.url).hostname;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return isDomainMatch(redirectHostname, currentHostname);
|
|
59
|
+
})());
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { defaultAllowCheck };
|
package/dist/allow.d.ts
ADDED
package/dist/allow.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { getTheme } from "./theme-CswaLtbW.js";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/base.ts
|
|
4
|
+
const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight.css");
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--color-background-dark: #0e0e11;
|
|
8
|
+
--color-background-light: #ffffff;
|
|
9
|
+
--color-primary-dark: #6772e5;
|
|
10
|
+
--color-primary-light: #6772e5;
|
|
11
|
+
|
|
12
|
+
--color-background-success-dark: oklch(0.3 0.04 172);
|
|
13
|
+
--color-background-success-light: oklch(
|
|
14
|
+
from var(--color-background-success-dark) 0.83 c h
|
|
15
|
+
);
|
|
16
|
+
--color-success-dark: oklch(
|
|
17
|
+
from var(--color-background-success-dark) 0.92 c h
|
|
18
|
+
);
|
|
19
|
+
--color-success-light: oklch(
|
|
20
|
+
from var(--color-background-success-dark) 0.25 c h
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
--color-background-error-dark: oklch(0.32 0.07 15);
|
|
24
|
+
--color-background-error-light: oklch(
|
|
25
|
+
from var(--color-background-error-dark) 0.92 c h
|
|
26
|
+
);
|
|
27
|
+
--color-error-dark: oklch(from var(--color-background-error-dark) 0.92 c h);
|
|
28
|
+
--color-error-light: oklch(from var(--color-background-error-dark) 0.25 c h);
|
|
29
|
+
|
|
30
|
+
--border-radius: 0;
|
|
31
|
+
|
|
32
|
+
--color-background: var(--color-background-dark);
|
|
33
|
+
--color-primary: var(--color-primary-dark);
|
|
34
|
+
|
|
35
|
+
--color-background-success: var(--color-background-success-dark);
|
|
36
|
+
--color-success: var(--color-success-dark);
|
|
37
|
+
--color-background-error: var(--color-background-error-dark);
|
|
38
|
+
--color-error: var(--color-error-dark);
|
|
39
|
+
|
|
40
|
+
@media (prefers-color-scheme: light) {
|
|
41
|
+
--color-background: var(--color-background-light);
|
|
42
|
+
--color-primary: var(--color-primary-light);
|
|
43
|
+
|
|
44
|
+
--color-background-success: var(--color-background-success-light);
|
|
45
|
+
--color-success: var(--color-success-light);
|
|
46
|
+
--color-background-error: var(--color-background-error-light);
|
|
47
|
+
--color-error: var(--color-error-light);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
--color-high: oklch(
|
|
51
|
+
from var(--color-background) clamp(0, calc((l - 0.714) * -1000), 1) 0 0
|
|
52
|
+
);
|
|
53
|
+
--color-low: oklch(
|
|
54
|
+
from var(--color-background) clamp(0, calc((l - 0.714) * 1000), 1) 0 0
|
|
55
|
+
);
|
|
56
|
+
--lightness-high: color-mix(
|
|
57
|
+
in oklch,
|
|
58
|
+
var(--color-high) 0%,
|
|
59
|
+
oklch(var(--color-high) 0 0)
|
|
60
|
+
);
|
|
61
|
+
--lightness-low: color-mix(
|
|
62
|
+
in oklch,
|
|
63
|
+
var(--color-low) 0%,
|
|
64
|
+
oklch(var(--color-low) 0 0)
|
|
65
|
+
);
|
|
66
|
+
--font-family:
|
|
67
|
+
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
68
|
+
"Segoe UI Symbol", "Noto Color Emoji";
|
|
69
|
+
--font-scale: 1;
|
|
70
|
+
|
|
71
|
+
--font-size-xs: calc(0.75rem * var(--font-scale));
|
|
72
|
+
--font-size-sm: calc(0.875rem * var(--font-scale));
|
|
73
|
+
--font-size-md: calc(1rem * var(--font-scale));
|
|
74
|
+
--font-size-lg: calc(1.125rem * var(--font-scale));
|
|
75
|
+
--font-size-xl: calc(1.25rem * var(--font-scale));
|
|
76
|
+
--font-size-2xl: calc(1.5rem * var(--font-scale));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-component="root"] {
|
|
80
|
+
font-family: var(--font-family);
|
|
81
|
+
background-color: var(--color-background);
|
|
82
|
+
padding: 1rem;
|
|
83
|
+
color: white;
|
|
84
|
+
position: absolute;
|
|
85
|
+
inset: 0;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
user-select: none;
|
|
91
|
+
color: var(--color-high);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[data-component="center"] {
|
|
95
|
+
width: 380px;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 1.5rem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-component="center"][data-size="small"] {
|
|
102
|
+
width: 300px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[data-component="link"] {
|
|
106
|
+
text-decoration: underline;
|
|
107
|
+
text-underline-offset: 0.125rem;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-component="label"] {
|
|
112
|
+
display: flex;
|
|
113
|
+
gap: 0.75rem;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
font-size: var(--font-size-xs);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[data-component="logo"] {
|
|
119
|
+
margin: 0 auto;
|
|
120
|
+
height: 2.5rem;
|
|
121
|
+
width: auto;
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@media (prefers-color-scheme: light) {
|
|
126
|
+
[data-component="logo"][data-mode="light"] {
|
|
127
|
+
display: block;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@media (prefers-color-scheme: dark) {
|
|
132
|
+
[data-component="logo"][data-mode="dark"] {
|
|
133
|
+
display: block;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[data-component="logo-default"] {
|
|
138
|
+
margin: 0 auto;
|
|
139
|
+
height: 2.5rem;
|
|
140
|
+
width: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@media (prefers-color-scheme: light) {
|
|
144
|
+
[data-component="logo-default"] {
|
|
145
|
+
color: var(--color-high);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (prefers-color-scheme: dark) {
|
|
150
|
+
[data-component="logo-default"] {
|
|
151
|
+
color: var(--color-high);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-component="input"] {
|
|
156
|
+
width: 100%;
|
|
157
|
+
height: 2.5rem;
|
|
158
|
+
padding: 0 1rem;
|
|
159
|
+
border: 1px solid transparent;
|
|
160
|
+
--background: oklch(
|
|
161
|
+
from var(--color-background)
|
|
162
|
+
calc(l + (-0.06 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.03)) c h
|
|
163
|
+
);
|
|
164
|
+
background: var(--background);
|
|
165
|
+
border-color: oklch(
|
|
166
|
+
from var(--color-background)
|
|
167
|
+
calc(
|
|
168
|
+
clamp(
|
|
169
|
+
0.22,
|
|
170
|
+
l +
|
|
171
|
+
(-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06),
|
|
172
|
+
0.88
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
c h
|
|
176
|
+
);
|
|
177
|
+
border-radius: calc(var(--border-radius) * 0.25rem);
|
|
178
|
+
font-size: var(--font-size-sm);
|
|
179
|
+
outline: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
[data-component="input"]:focus {
|
|
183
|
+
border-color: oklch(
|
|
184
|
+
from var(--color-background)
|
|
185
|
+
calc(
|
|
186
|
+
clamp(
|
|
187
|
+
0.3,
|
|
188
|
+
l +
|
|
189
|
+
(-0.2 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.1),
|
|
190
|
+
0.7
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
c h
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
[data-component="input"]:user-invalid:not(:focus) {
|
|
198
|
+
border-color: oklch(0.4 0.09 7.91);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[data-component="button"] {
|
|
202
|
+
height: 2.5rem;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
border: 0;
|
|
205
|
+
font-weight: 500;
|
|
206
|
+
font-size: var(--font-size-sm);
|
|
207
|
+
border-radius: calc(var(--border-radius) * 0.25rem);
|
|
208
|
+
display: flex;
|
|
209
|
+
gap: 0.75rem;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
background: var(--color-primary);
|
|
213
|
+
color: oklch(
|
|
214
|
+
from var(--color-primary) clamp(0, calc((l - 0.714) * -1000), 1) 0 0
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
[data-component="button"][data-color="ghost"] {
|
|
219
|
+
background: transparent;
|
|
220
|
+
color: var(--color-high);
|
|
221
|
+
border: 1px solid
|
|
222
|
+
oklch(
|
|
223
|
+
from var(--color-background)
|
|
224
|
+
calc(
|
|
225
|
+
clamp(
|
|
226
|
+
0.22,
|
|
227
|
+
l +
|
|
228
|
+
(-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06),
|
|
229
|
+
0.88
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
c h
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
[data-component="button"] [data-slot="icon"] {
|
|
237
|
+
width: 16px;
|
|
238
|
+
height: 16px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[data-component="button"] [data-slot="icon"] svg {
|
|
242
|
+
width: 100%;
|
|
243
|
+
height: 100%;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
[data-component="form"] {
|
|
247
|
+
max-width: 100%;
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
gap: 1rem;
|
|
251
|
+
margin: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
[data-component="form-alert"] {
|
|
255
|
+
height: 2.5rem;
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
padding: 0 1rem;
|
|
259
|
+
border-radius: calc(var(--border-radius) * 0.25rem);
|
|
260
|
+
background: var(--color-background-error);
|
|
261
|
+
color: var(--color-error);
|
|
262
|
+
text-align: left;
|
|
263
|
+
font-size: 0.75rem;
|
|
264
|
+
gap: 0.5rem;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
[data-component="form-alert"][data-color="success"] {
|
|
268
|
+
background: var(--color-background-success);
|
|
269
|
+
color: var(--color-success);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
[data-component="form-alert"][data-color="success"] [data-slot="icon-success"] {
|
|
273
|
+
display: block;
|
|
274
|
+
}
|
|
275
|
+
[data-component="form-alert"][data-color="success"] [data-slot="icon-danger"] {
|
|
276
|
+
display: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
[data-component="form-alert"]:has([data-slot="message"]:empty) {
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[data-component="form-alert"] [data-slot="icon-success"],
|
|
284
|
+
[data-component="form-alert"] [data-slot="icon-danger"] {
|
|
285
|
+
width: 1rem;
|
|
286
|
+
height: 1rem;
|
|
287
|
+
}
|
|
288
|
+
[data-component="form-alert"] [data-slot="icon-success"] {
|
|
289
|
+
display: none;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
[data-component="form-footer"] {
|
|
293
|
+
display: flex;
|
|
294
|
+
gap: 1rem;
|
|
295
|
+
font-size: 0.75rem;
|
|
296
|
+
align-items: center;
|
|
297
|
+
justify-content: center;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
[data-component="form-footer"]:has(> :nth-child(2)) {
|
|
301
|
+
justify-content: space-between;
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
/**
|
|
305
|
+
* Main layout component that wraps all authentication UI screens.
|
|
306
|
+
* Handles theming, logo display, and provides consistent styling.
|
|
307
|
+
*
|
|
308
|
+
* @param props - Layout props including children and optional size
|
|
309
|
+
* @returns Complete HTML document as a string with theming and branding applied
|
|
310
|
+
*/
|
|
311
|
+
const Layout = (props) => {
|
|
312
|
+
const theme = getTheme();
|
|
313
|
+
/**
|
|
314
|
+
* Gets a theme value for a specific key and color mode.
|
|
315
|
+
* Handles both string values and light/dark object configurations.
|
|
316
|
+
*/
|
|
317
|
+
const getThemeValue = (key, mode) => {
|
|
318
|
+
if (!theme?.[key]) return;
|
|
319
|
+
if (typeof theme[key] === "string") return theme[key];
|
|
320
|
+
return theme[key][mode];
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Calculates border radius value based on theme configuration.
|
|
324
|
+
*/
|
|
325
|
+
const getBorderRadius = () => {
|
|
326
|
+
switch (theme?.radius) {
|
|
327
|
+
case "none": return "0";
|
|
328
|
+
case "sm": return "1";
|
|
329
|
+
case "md": return "1.25";
|
|
330
|
+
case "lg": return "1.5";
|
|
331
|
+
case "full": return "1000000000001";
|
|
332
|
+
default: return "1";
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Checks if both light and dark logo variants are available.
|
|
337
|
+
*/
|
|
338
|
+
const hasCustomLogo = Boolean(getThemeValue("logo", "light") && getThemeValue("logo", "dark"));
|
|
339
|
+
/**
|
|
340
|
+
* CSS custom properties for theming.
|
|
341
|
+
*/
|
|
342
|
+
const themeStyles = [
|
|
343
|
+
`--color-background-light: ${getThemeValue("background", "light") || ""}`,
|
|
344
|
+
`--color-background-dark: ${getThemeValue("background", "dark") || ""}`,
|
|
345
|
+
`--color-primary-light: ${getThemeValue("primary", "light") || ""}`,
|
|
346
|
+
`--color-primary-dark: ${getThemeValue("primary", "dark") || ""}`,
|
|
347
|
+
`--font-family: ${theme?.font?.family || ""}`,
|
|
348
|
+
`--font-scale: ${theme?.font?.scale || ""}`,
|
|
349
|
+
`--border-radius: ${getBorderRadius()}`
|
|
350
|
+
].join("; ");
|
|
351
|
+
const faviconHtml = theme?.favicon ? `<link href="${theme.favicon}" rel="icon" />` : `
|
|
352
|
+
<link href="https://openauth.js.org/favicon.ico" rel="icon" sizes="48x48" />
|
|
353
|
+
<link href="https://openauth.js.org/favicon.svg" media="(prefers-color-scheme: light)" rel="icon" />
|
|
354
|
+
<link href="https://openauth.js.org/favicon-dark.svg" media="(prefers-color-scheme: dark)" rel="icon" />
|
|
355
|
+
<link href="https://openauth.js.org/favicon.svg" rel="shortcut icon" type="image/svg+xml" />
|
|
356
|
+
`;
|
|
357
|
+
const logoHtml = hasCustomLogo ? `
|
|
358
|
+
<img
|
|
359
|
+
alt="Logo Light"
|
|
360
|
+
data-component="logo"
|
|
361
|
+
data-mode="light"
|
|
362
|
+
src="${getThemeValue("logo", "light") || ""}"
|
|
363
|
+
/>
|
|
364
|
+
<img
|
|
365
|
+
alt="Logo Dark"
|
|
366
|
+
data-component="logo"
|
|
367
|
+
data-mode="dark"
|
|
368
|
+
src="${getThemeValue("logo", "dark") || ""}"
|
|
369
|
+
/>
|
|
370
|
+
` : DefaultDraftAuthLogo();
|
|
371
|
+
const childrenHtml = props.children || "";
|
|
372
|
+
return `
|
|
373
|
+
<!DOCTYPE html>
|
|
374
|
+
<html lang="en" style="${themeStyles}">
|
|
375
|
+
<head>
|
|
376
|
+
<title>${theme?.title || "Draft Auth"}</title>
|
|
377
|
+
<meta charset="utf-8" />
|
|
378
|
+
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
|
379
|
+
|
|
380
|
+
${faviconHtml}
|
|
381
|
+
|
|
382
|
+
<!-- Base CSS styles -->
|
|
383
|
+
<style>${css}</style>
|
|
384
|
+
|
|
385
|
+
<!-- Custom theme CSS if provided -->
|
|
386
|
+
${theme?.css ? `<style>${theme.css}</style>` : ""}
|
|
387
|
+
</head>
|
|
388
|
+
<body>
|
|
389
|
+
<div data-component="root">
|
|
390
|
+
<div data-component="center" data-size="${props.size || ""}">
|
|
391
|
+
${logoHtml}
|
|
392
|
+
${childrenHtml}
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</body>
|
|
396
|
+
</html>
|
|
397
|
+
`;
|
|
398
|
+
};
|
|
399
|
+
/**
|
|
400
|
+
* Default Draft Auth logo component.
|
|
401
|
+
* Used when no custom logo is provided in the theme configuration.
|
|
402
|
+
*/
|
|
403
|
+
const DefaultDraftAuthLogo = () => `
|
|
404
|
+
<svg
|
|
405
|
+
aria-label="Draft Auth Logo"
|
|
406
|
+
data-component="logo-default"
|
|
407
|
+
fill="none"
|
|
408
|
+
height="51"
|
|
409
|
+
viewBox="0 0 51 51"
|
|
410
|
+
width="51"
|
|
411
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
412
|
+
>
|
|
413
|
+
<title>Draft Auth Logo</title>
|
|
414
|
+
<path
|
|
415
|
+
d="M0 50.2303V0.12854H50.1017V50.2303H0ZM3.08002 11.8326H11.7041V3.20856H3.08002V11.8326ZM14.8526 11.8326H23.4766V3.20856H14.8526V11.8326ZM26.5566 11.8326H35.1807V3.20856H26.5566V11.8326ZM38.3292 11.8326H47.0217V3.20856H38.3292V11.8326ZM3.08002 23.6052H11.7041V14.9811H3.08002V23.6052ZM14.8526 23.6052H23.4766V14.9811H14.8526V23.6052ZM26.5566 23.6052H35.1807V14.9811H26.5566V23.6052ZM38.3292 23.6052H47.0217V14.9811H38.3292V23.6052ZM3.08002 35.3092H11.7041V26.6852H3.08002V35.3092ZM14.8526 35.3092H23.4766V26.6852H14.8526V35.3092ZM26.5566 35.3092H35.1807V26.6852H26.5566V35.3092ZM38.3292 35.3092H47.0217V26.6852H38.3292V35.3092ZM3.08002 47.1502H11.7041V38.3893H3.08002V47.1502ZM14.8526 47.1502H23.4766V38.3893H14.8526V47.1502ZM26.5566 47.1502H35.1807V38.3893H26.5566V47.1502ZM38.3292 47.1502H47.0217V38.3893H38.3292V47.1502Z"
|
|
416
|
+
fill="currentColor"
|
|
417
|
+
/>
|
|
418
|
+
</svg>
|
|
419
|
+
`;
|
|
420
|
+
|
|
421
|
+
//#endregion
|
|
422
|
+
export { Layout };
|