@draftlab/auth 0.0.3 → 0.0.4
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/allow.d.ts +58 -1
- package/dist/allow.js +61 -2
- package/dist/client.d.ts +2 -3
- package/dist/client.js +2 -2
- package/dist/core.d.ts +128 -8
- package/dist/core.js +496 -12
- package/dist/error.d.ts +242 -1
- package/dist/error.js +235 -1
- package/dist/index.d.ts +1 -8
- package/dist/index.js +1 -12
- package/dist/keys.d.ts +1 -1
- package/dist/keys.js +138 -3
- package/dist/pkce.js +160 -1
- package/dist/provider/code.d.ts +211 -3
- package/dist/provider/code.js +1 -1
- package/dist/provider/facebook.d.ts +2 -3
- package/dist/provider/facebook.js +1 -5
- package/dist/provider/github.d.ts +2 -3
- package/dist/provider/github.js +1 -5
- package/dist/provider/google.d.ts +2 -3
- package/dist/provider/google.js +1 -5
- package/dist/provider/oauth2.d.ts +175 -3
- package/dist/provider/oauth2.js +153 -5
- package/dist/provider/password.d.ts +384 -3
- package/dist/provider/password.js +4 -4
- package/dist/provider/provider.d.ts +226 -2
- package/dist/random.js +85 -1
- package/dist/storage/memory.d.ts +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/storage.d.ts +161 -1
- package/dist/storage/storage.js +60 -1
- package/dist/storage/turso.d.ts +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/unstorage.d.ts +1 -1
- package/dist/storage/unstorage.js +1 -1
- package/dist/subject.d.ts +61 -2
- package/dist/themes/theme.d.ts +208 -1
- package/dist/themes/theme.js +118 -1
- package/dist/ui/base.js +420 -2
- package/dist/ui/code.d.ts +1 -3
- package/dist/ui/code.js +3 -4
- package/dist/ui/form.js +59 -1
- package/dist/ui/icon.js +190 -1
- package/dist/ui/password.d.ts +1 -3
- package/dist/ui/password.js +2 -3
- package/dist/ui/select.js +278 -4
- package/dist/util.d.ts +71 -1
- package/dist/util.js +106 -1
- package/package.json +2 -2
- package/dist/allow-CixonwTW.d.ts +0 -59
- package/dist/allow-DX5cehSc.js +0 -63
- package/dist/base-DRutbxgL.js +0 -422
- package/dist/code-DJxdFR7p.d.ts +0 -212
- package/dist/core-BZHEAefX.d.ts +0 -129
- package/dist/core-CDM5o4rs.js +0 -498
- package/dist/error-CWAdNAzm.d.ts +0 -243
- package/dist/error-DgAKK7b2.js +0 -237
- package/dist/form-6XKM_cOk.js +0 -61
- package/dist/icon-Ci5uqGB_.js +0 -192
- package/dist/keys-EEfxEGfO.js +0 -140
- package/dist/oauth2-B7-6Z7Lc.js +0 -155
- package/dist/oauth2-CXHukHf2.d.ts +0 -176
- package/dist/password-C4KLmO0O.d.ts +0 -385
- package/dist/pkce-276Za_rZ.js +0 -162
- package/dist/provider-tndlqCzp.d.ts +0 -227
- package/dist/random-SXMYlaVr.js +0 -87
- package/dist/select-BjySLL8I.js +0 -280
- package/dist/storage-BEaqEPNQ.js +0 -62
- package/dist/storage-CxKerLlc.d.ts +0 -162
- package/dist/subject-DMIMVtaT.d.ts +0 -62
- package/dist/theme-C9by7VXf.d.ts +0 -209
- package/dist/theme-CswaLtbW.js +0 -120
- package/dist/util-CSdHUFOo.js +0 -108
- package/dist/util-DbSKG1Xm.d.ts +0 -72
package/dist/ui/password.d.ts
CHANGED
package/dist/ui/password.js
CHANGED
package/dist/ui/select.js
CHANGED
|
@@ -1,6 +1,280 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import "
|
|
3
|
-
import "../icon-Ci5uqGB_.js";
|
|
4
|
-
import { Select } from "../select-BjySLL8I.js";
|
|
1
|
+
import { Layout } from "./base.js";
|
|
2
|
+
import { ICON_GITHUB, ICON_GOOGLE } from "./icon.js";
|
|
5
3
|
|
|
4
|
+
//#region src/ui/select.ts
|
|
5
|
+
/**
|
|
6
|
+
* Default copy text used throughout the select UI.
|
|
7
|
+
* These values are used when no custom copy is provided.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_COPY = { button_provider: "Continue with" };
|
|
10
|
+
/**
|
|
11
|
+
* Default display names for all known provider types.
|
|
12
|
+
* These provide consistent naming across the application and serve as fallbacks
|
|
13
|
+
* when no custom display name is configured.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_DISPLAY = {
|
|
16
|
+
steam: "Steam",
|
|
17
|
+
twitch: "Twitch",
|
|
18
|
+
google: "Google",
|
|
19
|
+
github: "GitHub",
|
|
20
|
+
apple: "Apple",
|
|
21
|
+
code: "Code",
|
|
22
|
+
x: "X",
|
|
23
|
+
facebook: "Facebook",
|
|
24
|
+
microsoft: "Microsoft",
|
|
25
|
+
slack: "Slack",
|
|
26
|
+
password: "Password"
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Comprehensive icon mapping for all supported authentication providers.
|
|
30
|
+
* Each icon is an optimized SVG component with proper accessibility attributes.
|
|
31
|
+
*
|
|
32
|
+
* Icons are designed to:
|
|
33
|
+
* - Scale properly at different sizes
|
|
34
|
+
* - Inherit text color for theming
|
|
35
|
+
* - Include proper ARIA attributes
|
|
36
|
+
* - Work with screen readers
|
|
37
|
+
*/
|
|
38
|
+
const ICON = {
|
|
39
|
+
steam: `
|
|
40
|
+
<svg
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
class="bi bi-steam"
|
|
43
|
+
fill="currentColor"
|
|
44
|
+
height="16"
|
|
45
|
+
viewBox="0 0 16 16"
|
|
46
|
+
width="16"
|
|
47
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
+
>
|
|
49
|
+
<path d="M.329 10.333A8.01 8.01 0 0 0 7.99 16C12.414 16 16 12.418 16 8s-3.586-8-8.009-8A8.006 8.006 0 0 0 0 7.468l.003.006 4.304 1.769A2.2 2.2 0 0 1 5.62 8.88l1.96-2.844-.001-.04a3.046 3.046 0 0 1 3.042-3.043 3.046 3.046 0 0 1 3.042 3.043 3.047 3.047 0 0 1-3.111 3.044l-2.804 2a2.223 2.223 0 0 1-3.075 2.11 2.22 2.22 0 0 1-1.312-1.568L.33 10.333Z" />
|
|
50
|
+
<path d="M4.868 12.683a1.715 1.715 0 0 0 1.318-3.165 1.7 1.7 0 0 0-1.263-.02l1.023.424a1.261 1.261 0 1 1-.97 2.33l-.99-.41a1.7 1.7 0 0 0 .882.84Zm3.726-6.687a2.03 2.03 0 0 0 2.027 2.029 2.03 2.03 0 0 0 2.027-2.029 2.03 2.03 0 0 0-2.027-2.027 2.03 2.03 0 0 0-2.027 2.027m2.03-1.527a1.524 1.524 0 1 1-.002 3.048 1.524 1.524 0 0 1 .002-3.048" />
|
|
51
|
+
</svg>
|
|
52
|
+
`,
|
|
53
|
+
code: `
|
|
54
|
+
<svg
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
data-name="Layer 1"
|
|
57
|
+
fill="currentColor"
|
|
58
|
+
viewBox="0 0 52 52"
|
|
59
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
60
|
+
>
|
|
61
|
+
<path
|
|
62
|
+
d="M8.55,36.91A6.55,6.55,0,1,1,2,43.45,6.54,6.54,0,0,1,8.55,36.91Zm17.45,0a6.55,6.55,0,1,1-6.55,6.54A6.55,6.55,0,0,1,26,36.91Zm17.45,0a6.55,6.55,0,1,1-6.54,6.54A6.54,6.54,0,0,1,43.45,36.91ZM8.55,19.45A6.55,6.55,0,1,1,2,26,6.55,6.55,0,0,1,8.55,19.45Zm17.45,0A6.55,6.55,0,1,1,19.45,26,6.56,6.56,0,0,1,26,19.45Zm17.45,0A6.55,6.55,0,1,1,36.91,26,6.55,6.55,0,0,1,43.45,19.45ZM8.55,2A6.55,6.55,0,1,1,2,8.55,6.54,6.54,0,0,1,8.55,2ZM26,2a6.55,6.55,0,1,1-6.55,6.55A6.55,6.55,0,0,1,26,2ZM43.45,2a6.55,6.55,0,1,1-6.54,6.55A6.55,6.55,0,0,1,43.45,2Z"
|
|
63
|
+
fill-rule="evenodd"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
`,
|
|
67
|
+
password: `
|
|
68
|
+
<svg
|
|
69
|
+
aria-hidden="true"
|
|
70
|
+
fill="currentColor"
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
+
>
|
|
74
|
+
<path
|
|
75
|
+
clip-rule="evenodd"
|
|
76
|
+
d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z"
|
|
77
|
+
fill-rule="evenodd"
|
|
78
|
+
/>
|
|
79
|
+
</svg>
|
|
80
|
+
`,
|
|
81
|
+
twitch: `
|
|
82
|
+
<svg
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
role="img"
|
|
85
|
+
viewBox="0 0 448 512"
|
|
86
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
87
|
+
>
|
|
88
|
+
<path
|
|
89
|
+
d="M40.1 32L10 108.9v314.3h107V480h60.2l56.8-56.8h87l117-117V32H40.1zm357.8 254.1L331 353H224l-56.8 56.8V353H76.9V72.1h321v214zM331 149v116.9h-40.1V149H331zm-107 0v116.9h-40.1V149H224z"
|
|
90
|
+
fill="currentColor"
|
|
91
|
+
/>
|
|
92
|
+
</svg>
|
|
93
|
+
`,
|
|
94
|
+
google: ICON_GOOGLE,
|
|
95
|
+
github: ICON_GITHUB,
|
|
96
|
+
apple: `
|
|
97
|
+
<svg
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
role="img"
|
|
100
|
+
viewBox="0 0 814 1000"
|
|
101
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
102
|
+
>
|
|
103
|
+
<path
|
|
104
|
+
d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z "
|
|
105
|
+
fill="currentColor"
|
|
106
|
+
/>
|
|
107
|
+
</svg>
|
|
108
|
+
`,
|
|
109
|
+
x: `
|
|
110
|
+
<svg
|
|
111
|
+
aria-hidden="true"
|
|
112
|
+
role="img"
|
|
113
|
+
viewBox="0 0 1200 1227"
|
|
114
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
115
|
+
>
|
|
116
|
+
<path
|
|
117
|
+
d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
|
|
118
|
+
fill="currentColor"
|
|
119
|
+
/>
|
|
120
|
+
</svg>
|
|
121
|
+
`,
|
|
122
|
+
microsoft: `
|
|
123
|
+
<svg
|
|
124
|
+
aria-hidden="true"
|
|
125
|
+
preserveAspectRatio="xMidYMid"
|
|
126
|
+
role="img"
|
|
127
|
+
viewBox="0 0 256 256"
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
>
|
|
130
|
+
<path d="M121.666 121.666H0V0h121.666z" fill="#F1511B" />
|
|
131
|
+
<path d="M256 121.666H134.335V0H256z" fill="#80CC28" />
|
|
132
|
+
<path d="M121.663 256.002H0V134.336h121.663z" fill="#00ADEF" />
|
|
133
|
+
<path d="M256 256.002H134.335V134.336H256z" fill="#FBBC09" />
|
|
134
|
+
</svg>
|
|
135
|
+
`,
|
|
136
|
+
facebook: `
|
|
137
|
+
<svg
|
|
138
|
+
aria-hidden="true"
|
|
139
|
+
fill="url(#a)"
|
|
140
|
+
role="img"
|
|
141
|
+
viewBox="0 0 36 36"
|
|
142
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
143
|
+
>
|
|
144
|
+
<defs>
|
|
145
|
+
<linearGradient id="a" x1="50%" x2="50%" y1="97.078%" y2="0%">
|
|
146
|
+
<stop offset="0%" stop-color="#0062E0" />
|
|
147
|
+
<stop offset="100%" stop-color="#19AFFF" />
|
|
148
|
+
</linearGradient>
|
|
149
|
+
</defs>
|
|
150
|
+
<path d="M15 35.8C6.5 34.3 0 26.9 0 18 0 8.1 8.1 0 18 0s18 8.1 18 18c0 8.9-6.5 16.3-15 17.8l-1-.8h-4l-1 .8z" />
|
|
151
|
+
<path
|
|
152
|
+
d="m25 23 .8-5H21v-3.5c0-1.4.5-2.5 2.7-2.5H26V7.4c-1.3-.2-2.7-.4-4-.4-4.1 0-7 2.5-7 7v4h-4.5v5H15v12.7c1 .2 2 .3 3 .3s2-.1 3-.3V23h4z"
|
|
153
|
+
fill="#FFF"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
`,
|
|
157
|
+
slack: `
|
|
158
|
+
<svg
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
enable-background="new 0 0 2447.6 2452.5"
|
|
161
|
+
role="img"
|
|
162
|
+
viewBox="0 0 2447.6 2452.5"
|
|
163
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
164
|
+
>
|
|
165
|
+
<g clip-rule="evenodd" fill-rule="evenodd">
|
|
166
|
+
<path
|
|
167
|
+
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
|
168
|
+
fill="#36c5f0"
|
|
169
|
+
/>
|
|
170
|
+
<path
|
|
171
|
+
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
|
172
|
+
fill="#2eb67d"
|
|
173
|
+
/>
|
|
174
|
+
<path
|
|
175
|
+
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
|
176
|
+
fill="#ecb22e"
|
|
177
|
+
/>
|
|
178
|
+
<path
|
|
179
|
+
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
|
180
|
+
fill="#e01e5a"
|
|
181
|
+
/>
|
|
182
|
+
</g>
|
|
183
|
+
</svg>
|
|
184
|
+
`
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Creates a provider selection UI component for OAuth authentication.
|
|
188
|
+
*
|
|
189
|
+
* This component generates a complete authentication provider selection interface that:
|
|
190
|
+
* - Displays available OAuth providers as clickable buttons
|
|
191
|
+
* - Includes appropriate icons for recognized providers
|
|
192
|
+
* - Supports custom theming and internationalization
|
|
193
|
+
* - Provides accessible markup with proper ARIA attributes
|
|
194
|
+
* - Handles provider visibility and custom display names
|
|
195
|
+
*
|
|
196
|
+
* @param props - Configuration options for customizing the select UI
|
|
197
|
+
* @returns An async function that generates the HTML response for the selection interface
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* // Basic usage with defaults
|
|
202
|
+
* const selectUI = Select()
|
|
203
|
+
*
|
|
204
|
+
* // Customized with provider configuration
|
|
205
|
+
* const selectUI = Select({
|
|
206
|
+
* copy: {
|
|
207
|
+
* button_provider: "Sign in with"
|
|
208
|
+
* },
|
|
209
|
+
* displays: {
|
|
210
|
+
* github: "GitHub Account",
|
|
211
|
+
* code: "Email Code"
|
|
212
|
+
* },
|
|
213
|
+
* providers: {
|
|
214
|
+
* facebook: { hide: true },
|
|
215
|
+
* google: { display: "Google Workspace" }
|
|
216
|
+
* }
|
|
217
|
+
* })
|
|
218
|
+
*
|
|
219
|
+
* // Use in issuer configuration
|
|
220
|
+
* export default issuer({
|
|
221
|
+
* select: selectUI,
|
|
222
|
+
* // ... other configuration
|
|
223
|
+
* })
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* ## Provider Resolution Logic
|
|
227
|
+
*
|
|
228
|
+
* Display names are resolved in the following priority order:
|
|
229
|
+
* 1. Provider-specific `display` property
|
|
230
|
+
* 2. Global `displays` type override
|
|
231
|
+
* 3. Default display name from `DEFAULT_DISPLAY`
|
|
232
|
+
* 4. Provider type string as final fallback
|
|
233
|
+
*
|
|
234
|
+
* ## Accessibility Features
|
|
235
|
+
*
|
|
236
|
+
* The generated UI includes:
|
|
237
|
+
* - Semantic HTML structure
|
|
238
|
+
* - Proper button roles and keyboard navigation
|
|
239
|
+
* - Icon accessibility with `aria-hidden="true"`
|
|
240
|
+
* - Screen reader friendly provider names
|
|
241
|
+
*/
|
|
242
|
+
const Select = (props) => {
|
|
243
|
+
return async (providers, _req) => {
|
|
244
|
+
const copy = {
|
|
245
|
+
...DEFAULT_COPY,
|
|
246
|
+
...props?.copy
|
|
247
|
+
};
|
|
248
|
+
const displays = {
|
|
249
|
+
...DEFAULT_DISPLAY,
|
|
250
|
+
...props?.displays
|
|
251
|
+
};
|
|
252
|
+
const providerButtons = Object.entries(providers).map(([providerKey, providerType]) => {
|
|
253
|
+
const providerConfig = props?.providers?.[providerKey];
|
|
254
|
+
if (providerConfig?.hide) return "";
|
|
255
|
+
const displayName = providerConfig?.display || displays[providerType] || providerType;
|
|
256
|
+
const icon = ICON[providerKey];
|
|
257
|
+
return `
|
|
258
|
+
<a
|
|
259
|
+
aria-label="${copy.button_provider} ${displayName}"
|
|
260
|
+
data-color="ghost"
|
|
261
|
+
data-component="button"
|
|
262
|
+
href="./${providerKey}/authorize"
|
|
263
|
+
>
|
|
264
|
+
${icon ? `<i data-slot="icon">${icon}</i>` : ""}
|
|
265
|
+
${copy.button_provider} ${displayName}
|
|
266
|
+
</a>
|
|
267
|
+
`;
|
|
268
|
+
}).filter((button) => button !== "").join("");
|
|
269
|
+
const formContent = `
|
|
270
|
+
<div data-component="form">
|
|
271
|
+
${providerButtons}
|
|
272
|
+
</div>
|
|
273
|
+
`;
|
|
274
|
+
const html = Layout({ children: formContent });
|
|
275
|
+
return new Response(html, { headers: { "Content-Type": "text/html" } });
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
6
280
|
export { Select };
|
package/dist/util.d.ts
CHANGED
|
@@ -1,2 +1,72 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouterContext } from "@draftlab/auth-router/types";
|
|
2
|
+
|
|
3
|
+
//#region src/util.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility type that flattens complex types for better IntelliSense display.
|
|
7
|
+
* Converts intersections and complex mapped types into cleaner object types.
|
|
8
|
+
*
|
|
9
|
+
* @template T - The type to prettify
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* type Complex = { a: string } & { b: number }
|
|
14
|
+
* type Clean = Prettify<Complex> // { a: string; b: number }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a complete URL relative to the current request context.
|
|
20
|
+
* Handles proxy headers (x-forwarded-*) to ensure correct URL generation
|
|
21
|
+
* in containerized and load-balanced environments.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Router context containing request information
|
|
24
|
+
* @param path - Relative path to append to the base URL
|
|
25
|
+
* @returns Complete URL string with proper protocol, host, and port
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const callbackUrl = getRelativeUrl(ctx, "/callback")
|
|
30
|
+
* // Returns: "https://myapp.com/auth/callback"
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare const getRelativeUrl: (ctx: RouterContext, path: string) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Determines if two domain names are considered a match for security purposes.
|
|
36
|
+
* Uses effective TLD+1 matching to allow subdomains while preventing
|
|
37
|
+
* unauthorized cross-domain requests.
|
|
38
|
+
*
|
|
39
|
+
* @param a - First domain name to compare
|
|
40
|
+
* @param b - Second domain name to compare
|
|
41
|
+
* @returns True if domains are considered a security match
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* isDomainMatch("app.example.com", "auth.example.com") // true
|
|
46
|
+
* isDomainMatch("example.com", "evil.com") // false
|
|
47
|
+
* isDomainMatch("app.co.uk", "auth.co.uk") // true (handles two-part TLD)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare const isDomainMatch: (a: string, b: string) => boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Creates a lazy-evaluated function that caches the result of the first execution.
|
|
53
|
+
* Subsequent calls return the cached value without re-executing the function.
|
|
54
|
+
*
|
|
55
|
+
* @template T - The return type of the lazy function
|
|
56
|
+
* @param fn - Function to execute lazily
|
|
57
|
+
* @returns Function that returns the cached result
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const expensiveOperation = lazy(() => {
|
|
62
|
+
* // Computing... (only logs once)
|
|
63
|
+
* return heavyComputation()
|
|
64
|
+
* })
|
|
65
|
+
*
|
|
66
|
+
* const result1 = expensiveOperation() // Executes and caches
|
|
67
|
+
* const result2 = expensiveOperation() // Returns cached value
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare const lazy: <T>(fn: () => T) => (() => T);
|
|
71
|
+
//#endregion
|
|
2
72
|
export { Prettify, getRelativeUrl, isDomainMatch, lazy };
|
package/dist/util.js
CHANGED
|
@@ -1,3 +1,108 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/util.ts
|
|
2
|
+
/**
|
|
3
|
+
* Constructs a complete URL relative to the current request context.
|
|
4
|
+
* Handles proxy headers (x-forwarded-*) to ensure correct URL generation
|
|
5
|
+
* in containerized and load-balanced environments.
|
|
6
|
+
*
|
|
7
|
+
* @param ctx - Router context containing request information
|
|
8
|
+
* @param path - Relative path to append to the base URL
|
|
9
|
+
* @returns Complete URL string with proper protocol, host, and port
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const callbackUrl = getRelativeUrl(ctx, "/callback")
|
|
14
|
+
* // Returns: "https://myapp.com/auth/callback"
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const getRelativeUrl = (ctx, path) => {
|
|
18
|
+
const result = new URL(path, ctx.request.url);
|
|
19
|
+
result.host = ctx.header("x-forwarded-host") || result.host;
|
|
20
|
+
result.protocol = ctx.header("x-forwarded-proto") || result.protocol;
|
|
21
|
+
result.port = ctx.header("x-forwarded-port") || result.port;
|
|
22
|
+
return result.toString();
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* List of known two-part top-level domains that require special handling
|
|
26
|
+
* for domain matching. These domains have an additional level that should
|
|
27
|
+
* be considered when determining effective domain boundaries.
|
|
28
|
+
*/
|
|
29
|
+
const twoPartTlds = [
|
|
30
|
+
"co.uk",
|
|
31
|
+
"co.jp",
|
|
32
|
+
"co.kr",
|
|
33
|
+
"co.nz",
|
|
34
|
+
"co.za",
|
|
35
|
+
"co.in",
|
|
36
|
+
"com.au",
|
|
37
|
+
"com.br",
|
|
38
|
+
"com.cn",
|
|
39
|
+
"com.mx",
|
|
40
|
+
"com.tw",
|
|
41
|
+
"net.au",
|
|
42
|
+
"org.uk",
|
|
43
|
+
"ne.jp",
|
|
44
|
+
"ac.uk",
|
|
45
|
+
"gov.uk",
|
|
46
|
+
"edu.au",
|
|
47
|
+
"gov.au"
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Determines if two domain names are considered a match for security purposes.
|
|
51
|
+
* Uses effective TLD+1 matching to allow subdomains while preventing
|
|
52
|
+
* unauthorized cross-domain requests.
|
|
53
|
+
*
|
|
54
|
+
* @param a - First domain name to compare
|
|
55
|
+
* @param b - Second domain name to compare
|
|
56
|
+
* @returns True if domains are considered a security match
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* isDomainMatch("app.example.com", "auth.example.com") // true
|
|
61
|
+
* isDomainMatch("example.com", "evil.com") // false
|
|
62
|
+
* isDomainMatch("app.co.uk", "auth.co.uk") // true (handles two-part TLD)
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
const isDomainMatch = (a, b) => {
|
|
66
|
+
if (a === b) return true;
|
|
67
|
+
const partsA = a.split(".");
|
|
68
|
+
const partsB = b.split(".");
|
|
69
|
+
const hasTwoPartTld = twoPartTlds.some((tld) => a.endsWith(`.${tld}`) || b.endsWith(`.${tld}`));
|
|
70
|
+
const numParts = hasTwoPartTld ? -3 : -2;
|
|
71
|
+
const min = Math.min(partsA.length, partsB.length, numParts);
|
|
72
|
+
const tailA = partsA.slice(min).join(".");
|
|
73
|
+
const tailB = partsB.slice(min).join(".");
|
|
74
|
+
return tailA === tailB;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Creates a lazy-evaluated function that caches the result of the first execution.
|
|
78
|
+
* Subsequent calls return the cached value without re-executing the function.
|
|
79
|
+
*
|
|
80
|
+
* @template T - The return type of the lazy function
|
|
81
|
+
* @param fn - Function to execute lazily
|
|
82
|
+
* @returns Function that returns the cached result
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const expensiveOperation = lazy(() => {
|
|
87
|
+
* // Computing... (only logs once)
|
|
88
|
+
* return heavyComputation()
|
|
89
|
+
* })
|
|
90
|
+
*
|
|
91
|
+
* const result1 = expensiveOperation() // Executes and caches
|
|
92
|
+
* const result2 = expensiveOperation() // Returns cached value
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
const lazy = (fn) => {
|
|
96
|
+
let value;
|
|
97
|
+
let hasValue = false;
|
|
98
|
+
return () => {
|
|
99
|
+
if (!hasValue) {
|
|
100
|
+
value = fn();
|
|
101
|
+
hasValue = true;
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
2
106
|
|
|
107
|
+
//#endregion
|
|
3
108
|
export { getRelativeUrl, isDomainMatch, lazy };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draftlab/auth",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core implementation for @draftlab/auth",
|
|
6
6
|
"author": "Matheus Pergoli",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@standard-schema/spec": "^1.0.0",
|
|
59
59
|
"jose": "^6.0.11",
|
|
60
|
-
"@draftlab/auth-router": "0.0.
|
|
60
|
+
"@draftlab/auth-router": "0.0.2"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">=18"
|
package/dist/allow-CixonwTW.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
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 };
|
package/dist/allow-DX5cehSc.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
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 };
|