@codelitdev/oauth-server-kit 0.5.0 → 0.6.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/express.d.mts +9 -1
- package/dist/express.d.ts +9 -1
- package/dist/express.js +14 -5
- package/dist/express.mjs +14 -5
- package/package.json +1 -1
package/dist/express.d.mts
CHANGED
|
@@ -24,6 +24,13 @@ type HostedLoginMethod = {
|
|
|
24
24
|
providerId: string;
|
|
25
25
|
label: string;
|
|
26
26
|
};
|
|
27
|
+
/** A bot check the email-OTP form completes before asking for a code. The
|
|
28
|
+
* page sends its token in the `x-captcha-response` header, which Better
|
|
29
|
+
* Auth's `captcha` plugin verifies with the provider's secret key. */
|
|
30
|
+
type HostedLoginCaptcha = {
|
|
31
|
+
provider: "cloudflare-turnstile";
|
|
32
|
+
siteKey: string;
|
|
33
|
+
};
|
|
27
34
|
interface CreateOAuthPagesRouterOptions {
|
|
28
35
|
appName: string;
|
|
29
36
|
authBasePath: string;
|
|
@@ -34,7 +41,8 @@ interface CreateOAuthPagesRouterOptions {
|
|
|
34
41
|
logoUrl?: string;
|
|
35
42
|
faviconUrl?: string;
|
|
36
43
|
primaryColor?: string;
|
|
44
|
+
captcha?: HostedLoginCaptcha;
|
|
37
45
|
}
|
|
38
46
|
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
39
47
|
|
|
40
|
-
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
48
|
+
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginCaptcha, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
package/dist/express.d.ts
CHANGED
|
@@ -24,6 +24,13 @@ type HostedLoginMethod = {
|
|
|
24
24
|
providerId: string;
|
|
25
25
|
label: string;
|
|
26
26
|
};
|
|
27
|
+
/** A bot check the email-OTP form completes before asking for a code. The
|
|
28
|
+
* page sends its token in the `x-captcha-response` header, which Better
|
|
29
|
+
* Auth's `captcha` plugin verifies with the provider's secret key. */
|
|
30
|
+
type HostedLoginCaptcha = {
|
|
31
|
+
provider: "cloudflare-turnstile";
|
|
32
|
+
siteKey: string;
|
|
33
|
+
};
|
|
27
34
|
interface CreateOAuthPagesRouterOptions {
|
|
28
35
|
appName: string;
|
|
29
36
|
authBasePath: string;
|
|
@@ -34,7 +41,8 @@ interface CreateOAuthPagesRouterOptions {
|
|
|
34
41
|
logoUrl?: string;
|
|
35
42
|
faviconUrl?: string;
|
|
36
43
|
primaryColor?: string;
|
|
44
|
+
captcha?: HostedLoginCaptcha;
|
|
37
45
|
}
|
|
38
46
|
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
39
47
|
|
|
40
|
-
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
48
|
+
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginCaptcha, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
package/dist/express.js
CHANGED
|
@@ -294,6 +294,14 @@ function normalizeOptions(options) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
if (options.captcha) {
|
|
298
|
+
if (options.captcha.provider !== "cloudflare-turnstile") {
|
|
299
|
+
throw new Error("captcha provider must be cloudflare-turnstile");
|
|
300
|
+
}
|
|
301
|
+
if (!/^[A-Za-z0-9_-]+$/.test(options.captcha.siteKey)) {
|
|
302
|
+
throw new Error("captcha siteKey must be a Turnstile site key");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
297
305
|
for (const name of (_a = options.legacyHostOnlySessionCookieNames) != null ? _a : []) {
|
|
298
306
|
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
299
307
|
throw new Error("legacy cookie names contain invalid characters");
|
|
@@ -315,7 +323,7 @@ html{--brand-primary:#8c7a6b;color-scheme:light}*{box-sizing:border-box}body{mar
|
|
|
315
323
|
h1{margin:0 0 24px;text-align:center;font-size:22px;font-weight:650}.sub{margin:-12px 0 24px;text-align:center;color:#71717a;font-size:13px;line-height:1.5}.error{display:none;margin-bottom:16px;padding:10px 12px;border-radius:8px;background:#fef2f2;color:#b42318;font-size:13px}
|
|
316
324
|
form{margin:0}.field{margin-bottom:14px}label{display:block;margin-bottom:6px;font-size:12px;color:#52525b}input{width:100%;padding:11px 13px;border:1px solid #d4d4d8;border-radius:8px;background:#fff;color:#171717;font-size:15px}input:focus{outline:2px solid var(--brand-primary);outline-offset:1px;border-color:transparent}
|
|
317
325
|
button{width:100%;margin-top:8px;padding:11px;border:1px solid #d4d4d8;border-radius:8px;background:#fafafa;color:#171717;font-size:14px;font-weight:550;cursor:pointer}button:focus-visible{outline:2px solid var(--brand-primary);outline-offset:2px}.primary{border-color:var(--brand-primary);background:var(--brand-primary);color:#fff}.secondary{color:#52525b;background:transparent}.social{display:inline-flex;align-items:center;justify-content:center;gap:10px;background:#fff;color:#1f1f1f;border-color:#dadce0;font-weight:500}.social:hover{background:#f7f8f8}.social svg{width:18px;height:18px;flex:0 0 18px}
|
|
318
|
-
.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:#a1a1aa;font-size:12px}.divider:before,.divider:after{content:"";flex:1;height:1px;background:#e4e4e7}
|
|
326
|
+
.captcha{margin-bottom:6px;min-height:65px}.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:#a1a1aa;font-size:12px}.divider:before,.divider:after{content:"";flex:1;height:1px;background:#e4e4e7}
|
|
319
327
|
button:disabled{opacity:.55;cursor:not-allowed}.client{margin:20px 0;padding:14px;border:1px solid #e4e4e7;border-radius:9px;background:#fafafa}.client-name{font-weight:600;word-break:break-all}.scopes{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}.scope{padding:4px 7px;border:1px solid #e4e4e7;border-radius:6px;color:#52525b;font-size:12px}.actions{display:flex;gap:8px}.actions button{margin:0}
|
|
320
328
|
`;
|
|
321
329
|
function page(title, body, options) {
|
|
@@ -359,7 +367,8 @@ function loginMarkup(options) {
|
|
|
359
367
|
const socialMethods = options.loginMethods.filter(
|
|
360
368
|
(method) => method.type === "social"
|
|
361
369
|
);
|
|
362
|
-
const
|
|
370
|
+
const captcha = emailMethod && options.captcha ? `<div class="captcha"><div class="cf-turnstile" data-sitekey="${escapeHtml(options.captcha.siteKey)}" data-theme="light" data-size="flexible"></div></div><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>` : "";
|
|
371
|
+
const email = emailMethod ? `<form id="email-form"><div class="field"><label for="email">Email address</label><input id="email" type="email" required autocomplete="email"></div>${captcha}<button class="primary" type="submit">${escapeHtml((_a = emailMethod.label) != null ? _a : "Continue with email")}</button></form><form id="otp-form" hidden><div class="field"><label for="otp">Verification code</label><input id="otp" inputmode="numeric" required autocomplete="one-time-code"></div><button class="primary" type="submit">Sign in</button><button class="secondary" type="button" id="change-email">Use a different email</button></form>` : "";
|
|
363
372
|
const social = socialMethods.map(socialButton).join("");
|
|
364
373
|
const divider = email && social ? '<div class="divider">or</div>' : "";
|
|
365
374
|
return `${brandMark(options)}<h1>Sign in to ${escapeHtml(options.appName)}</h1><div class="error" id="error" role="alert"></div>${email}${divider}${social}`;
|
|
@@ -368,12 +377,12 @@ function loginScript(options, oauth, redirect) {
|
|
|
368
377
|
const authBasePath = scriptLiteral(options.authBasePath);
|
|
369
378
|
const redirectTarget = scriptLiteral(redirect != null ? redirect : "");
|
|
370
379
|
return `<script>(function(){
|
|
371
|
-
var authBasePath=${authBasePath};var oauth=${oauth};var oauthQuery=location.search.slice(1);var redirectTarget=${redirectTarget};var errorEl=document.getElementById("error");
|
|
380
|
+
var authBasePath=${authBasePath};var oauth=${oauth};var captcha=${Boolean(options.captcha)};var oauthQuery=location.search.slice(1);var redirectTarget=${redirectTarget};var errorEl=document.getElementById("error");
|
|
372
381
|
function showError(message){errorEl.textContent=message||"Sign-in failed. Please try again.";errorEl.style.display="block"}function clearError(){errorEl.style.display="none"}
|
|
373
|
-
async function post(path,body){var response=await fetch(authBasePath+path,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(body)});var data={};try{data=await response.json()}catch{}if(!response.ok)throw new Error(typeof data.message==="string"&&data.message?data.message:"Sign-in failed. Please try again.");return data}
|
|
382
|
+
async function post(path,body,headers){var response=await fetch(authBasePath+path,{method:"POST",headers:{"content-type":"application/json",accept:"application/json",...headers},body:JSON.stringify(body)});var data={};try{data=await response.json()}catch{}if(!response.ok)throw new Error(typeof data.message==="string"&&data.message?data.message:"Sign-in failed. Please try again.");return data}
|
|
374
383
|
function follow(data){var target=data.url||data.redirect_uri||redirectTarget;if(!target)throw new Error("Sign-in failed. Please try again.");location.assign(target)}
|
|
375
384
|
var emailForm=document.getElementById("email-form"),otpForm=document.getElementById("otp-form"),email="";
|
|
376
|
-
if(emailForm){emailForm.addEventListener("submit",async function(event){event.preventDefault();clearError();email=emailForm.querySelector(
|
|
385
|
+
if(emailForm){emailForm.addEventListener("submit",async function(event){event.preventDefault();clearError();email=document.getElementById("email").value;var headers={};if(captcha){var token=emailForm.querySelector('input[name="cf-turnstile-response"]');if(!token||!token.value){showError("Complete the security check, then try again.");return}headers["x-captcha-response"]=token.value}try{await post("/email-otp/send-verification-otp",{email:email,type:"sign-in",...(oauth?{oauth_query:oauthQuery}:{})},headers);emailForm.hidden=true;otpForm.hidden=false}catch(error){showError(error.message)}finally{if(captcha&&typeof turnstile!=="undefined")turnstile.reset()}});otpForm.addEventListener("submit",async function(event){event.preventDefault();clearError();try{var data=await post("/sign-in/email-otp",{email:email,otp:document.getElementById("otp").value,name:email.split("@")[0],...(oauth?{oauth_query:oauthQuery}:{})});follow(data)}catch(error){showError(error.message)}});document.getElementById("change-email").addEventListener("click",function(){otpForm.hidden=true;emailForm.hidden=false;clearError()})}
|
|
377
386
|
document.querySelectorAll("button.social").forEach(function(button){button.addEventListener("click",async function(){clearError();button.disabled=true;try{var body={provider:button.dataset.provider,...(oauth?{oauth_query:oauthQuery}:{callbackURL:redirectTarget,errorCallbackURL:location.href})};follow(await post("/sign-in/social",body))}catch(error){showError(error.message);button.disabled=false}})});
|
|
378
387
|
})();</script>`;
|
|
379
388
|
}
|
package/dist/express.mjs
CHANGED
|
@@ -181,6 +181,14 @@ function normalizeOptions(options) {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
if (options.captcha) {
|
|
185
|
+
if (options.captcha.provider !== "cloudflare-turnstile") {
|
|
186
|
+
throw new Error("captcha provider must be cloudflare-turnstile");
|
|
187
|
+
}
|
|
188
|
+
if (!/^[A-Za-z0-9_-]+$/.test(options.captcha.siteKey)) {
|
|
189
|
+
throw new Error("captcha siteKey must be a Turnstile site key");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
184
192
|
for (const name of (_a = options.legacyHostOnlySessionCookieNames) != null ? _a : []) {
|
|
185
193
|
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
186
194
|
throw new Error("legacy cookie names contain invalid characters");
|
|
@@ -202,7 +210,7 @@ html{--brand-primary:#8c7a6b;color-scheme:light}*{box-sizing:border-box}body{mar
|
|
|
202
210
|
h1{margin:0 0 24px;text-align:center;font-size:22px;font-weight:650}.sub{margin:-12px 0 24px;text-align:center;color:#71717a;font-size:13px;line-height:1.5}.error{display:none;margin-bottom:16px;padding:10px 12px;border-radius:8px;background:#fef2f2;color:#b42318;font-size:13px}
|
|
203
211
|
form{margin:0}.field{margin-bottom:14px}label{display:block;margin-bottom:6px;font-size:12px;color:#52525b}input{width:100%;padding:11px 13px;border:1px solid #d4d4d8;border-radius:8px;background:#fff;color:#171717;font-size:15px}input:focus{outline:2px solid var(--brand-primary);outline-offset:1px;border-color:transparent}
|
|
204
212
|
button{width:100%;margin-top:8px;padding:11px;border:1px solid #d4d4d8;border-radius:8px;background:#fafafa;color:#171717;font-size:14px;font-weight:550;cursor:pointer}button:focus-visible{outline:2px solid var(--brand-primary);outline-offset:2px}.primary{border-color:var(--brand-primary);background:var(--brand-primary);color:#fff}.secondary{color:#52525b;background:transparent}.social{display:inline-flex;align-items:center;justify-content:center;gap:10px;background:#fff;color:#1f1f1f;border-color:#dadce0;font-weight:500}.social:hover{background:#f7f8f8}.social svg{width:18px;height:18px;flex:0 0 18px}
|
|
205
|
-
.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:#a1a1aa;font-size:12px}.divider:before,.divider:after{content:"";flex:1;height:1px;background:#e4e4e7}
|
|
213
|
+
.captcha{margin-bottom:6px;min-height:65px}.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:#a1a1aa;font-size:12px}.divider:before,.divider:after{content:"";flex:1;height:1px;background:#e4e4e7}
|
|
206
214
|
button:disabled{opacity:.55;cursor:not-allowed}.client{margin:20px 0;padding:14px;border:1px solid #e4e4e7;border-radius:9px;background:#fafafa}.client-name{font-weight:600;word-break:break-all}.scopes{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}.scope{padding:4px 7px;border:1px solid #e4e4e7;border-radius:6px;color:#52525b;font-size:12px}.actions{display:flex;gap:8px}.actions button{margin:0}
|
|
207
215
|
`;
|
|
208
216
|
function page(title, body, options) {
|
|
@@ -246,7 +254,8 @@ function loginMarkup(options) {
|
|
|
246
254
|
const socialMethods = options.loginMethods.filter(
|
|
247
255
|
(method) => method.type === "social"
|
|
248
256
|
);
|
|
249
|
-
const
|
|
257
|
+
const captcha = emailMethod && options.captcha ? `<div class="captcha"><div class="cf-turnstile" data-sitekey="${escapeHtml(options.captcha.siteKey)}" data-theme="light" data-size="flexible"></div></div><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>` : "";
|
|
258
|
+
const email = emailMethod ? `<form id="email-form"><div class="field"><label for="email">Email address</label><input id="email" type="email" required autocomplete="email"></div>${captcha}<button class="primary" type="submit">${escapeHtml((_a = emailMethod.label) != null ? _a : "Continue with email")}</button></form><form id="otp-form" hidden><div class="field"><label for="otp">Verification code</label><input id="otp" inputmode="numeric" required autocomplete="one-time-code"></div><button class="primary" type="submit">Sign in</button><button class="secondary" type="button" id="change-email">Use a different email</button></form>` : "";
|
|
250
259
|
const social = socialMethods.map(socialButton).join("");
|
|
251
260
|
const divider = email && social ? '<div class="divider">or</div>' : "";
|
|
252
261
|
return `${brandMark(options)}<h1>Sign in to ${escapeHtml(options.appName)}</h1><div class="error" id="error" role="alert"></div>${email}${divider}${social}`;
|
|
@@ -255,12 +264,12 @@ function loginScript(options, oauth, redirect) {
|
|
|
255
264
|
const authBasePath = scriptLiteral(options.authBasePath);
|
|
256
265
|
const redirectTarget = scriptLiteral(redirect != null ? redirect : "");
|
|
257
266
|
return `<script>(function(){
|
|
258
|
-
var authBasePath=${authBasePath};var oauth=${oauth};var oauthQuery=location.search.slice(1);var redirectTarget=${redirectTarget};var errorEl=document.getElementById("error");
|
|
267
|
+
var authBasePath=${authBasePath};var oauth=${oauth};var captcha=${Boolean(options.captcha)};var oauthQuery=location.search.slice(1);var redirectTarget=${redirectTarget};var errorEl=document.getElementById("error");
|
|
259
268
|
function showError(message){errorEl.textContent=message||"Sign-in failed. Please try again.";errorEl.style.display="block"}function clearError(){errorEl.style.display="none"}
|
|
260
|
-
async function post(path,body){var response=await fetch(authBasePath+path,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(body)});var data={};try{data=await response.json()}catch{}if(!response.ok)throw new Error(typeof data.message==="string"&&data.message?data.message:"Sign-in failed. Please try again.");return data}
|
|
269
|
+
async function post(path,body,headers){var response=await fetch(authBasePath+path,{method:"POST",headers:{"content-type":"application/json",accept:"application/json",...headers},body:JSON.stringify(body)});var data={};try{data=await response.json()}catch{}if(!response.ok)throw new Error(typeof data.message==="string"&&data.message?data.message:"Sign-in failed. Please try again.");return data}
|
|
261
270
|
function follow(data){var target=data.url||data.redirect_uri||redirectTarget;if(!target)throw new Error("Sign-in failed. Please try again.");location.assign(target)}
|
|
262
271
|
var emailForm=document.getElementById("email-form"),otpForm=document.getElementById("otp-form"),email="";
|
|
263
|
-
if(emailForm){emailForm.addEventListener("submit",async function(event){event.preventDefault();clearError();email=emailForm.querySelector(
|
|
272
|
+
if(emailForm){emailForm.addEventListener("submit",async function(event){event.preventDefault();clearError();email=document.getElementById("email").value;var headers={};if(captcha){var token=emailForm.querySelector('input[name="cf-turnstile-response"]');if(!token||!token.value){showError("Complete the security check, then try again.");return}headers["x-captcha-response"]=token.value}try{await post("/email-otp/send-verification-otp",{email:email,type:"sign-in",...(oauth?{oauth_query:oauthQuery}:{})},headers);emailForm.hidden=true;otpForm.hidden=false}catch(error){showError(error.message)}finally{if(captcha&&typeof turnstile!=="undefined")turnstile.reset()}});otpForm.addEventListener("submit",async function(event){event.preventDefault();clearError();try{var data=await post("/sign-in/email-otp",{email:email,otp:document.getElementById("otp").value,name:email.split("@")[0],...(oauth?{oauth_query:oauthQuery}:{})});follow(data)}catch(error){showError(error.message)}});document.getElementById("change-email").addEventListener("click",function(){otpForm.hidden=true;emailForm.hidden=false;clearError()})}
|
|
264
273
|
document.querySelectorAll("button.social").forEach(function(button){button.addEventListener("click",async function(){clearError();button.disabled=true;try{var body={provider:button.dataset.provider,...(oauth?{oauth_query:oauthQuery}:{callbackURL:redirectTarget,errorCallbackURL:location.href})};follow(await post("/sign-in/social",body))}catch(error){showError(error.message);button.disabled=false}})});
|
|
265
274
|
})();</script>`;
|
|
266
275
|
}
|
package/package.json
CHANGED