@rimelight/security 0.0.19 → 0.0.21
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/config/index.d.mts +1 -1
- package/dist/csp.d.mts +1 -1
- package/dist/csp.mjs +14 -9
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +3 -1
- package/dist/integration.d.mts +26 -0
- package/dist/integration.mjs +20 -0
- package/dist/middleware/construction.d.mts +1 -1
- package/dist/middleware/ratelimit.d.mts +2 -2
- package/dist/middleware/security.d.mts +1 -1
- package/dist/turnstile.d.mts +53 -0
- package/dist/turnstile.mjs +92 -0
- package/dist/{types-Dcvwj-af.d.mts → types-CL5HooHU.d.mts} +5 -0
- package/dist/types.d.mts +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +16 -3
- package/src/components/ConstructionPage.astro +166 -0
- package/src/components/ConstructionSignIn.astro +9 -9
- package/src/components/Turnstile.astro +94 -0
- package/src/config/index.ts +2 -0
- package/src/csp.ts +106 -0
- package/src/index.ts +6 -0
- package/src/integration.ts +51 -0
- package/src/layouts/ConstructionLayout.astro +50 -0
- package/src/middleware/construction.ts +315 -0
- package/src/middleware/dev-only.ts +16 -0
- package/src/middleware/index.ts +4 -0
- package/src/middleware/ratelimit.ts +120 -0
- package/src/middleware/security.ts +288 -0
- package/src/pages/construction.astro +5 -0
- package/src/turnstile.ts +172 -0
- package/src/types.ts +213 -0
- package/src/virtual.d.ts +16 -0
- package/src/vite.ts +114 -0
package/dist/config/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "../types-
|
|
1
|
+
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "../types-CL5HooHU.mjs";
|
|
2
2
|
import { buildCspHeader } from "../csp.mjs";
|
|
3
3
|
export { ConstructionOptions, RateLimitOptions, RateLimiterBinding, SecurityOptions, buildCspHeader };
|
package/dist/csp.d.mts
CHANGED
package/dist/csp.mjs
CHANGED
|
@@ -4,27 +4,31 @@
|
|
|
4
4
|
*/
|
|
5
5
|
function buildCspHeader(options = {}, nonce) {
|
|
6
6
|
const domain = options.domain || "";
|
|
7
|
+
const imgSources = Array.from(/* @__PURE__ */ new Set([
|
|
8
|
+
"'self'",
|
|
9
|
+
"data:",
|
|
10
|
+
...domain ? [`https://cdn.${domain}`] : [],
|
|
11
|
+
"https://i3.ytimg.com",
|
|
12
|
+
"https://www.youtube.com",
|
|
13
|
+
"https://www.youtube-nocookie.com",
|
|
14
|
+
...options.imgSrc || []
|
|
15
|
+
]));
|
|
16
|
+
const allowTurnstile = options.turnstile !== false;
|
|
7
17
|
const defaultDirectives = {
|
|
8
18
|
"default-src": ["'none'"],
|
|
9
|
-
"img-src":
|
|
10
|
-
"'self'",
|
|
11
|
-
"data:",
|
|
12
|
-
...domain ? [`https://cdn.${domain}`] : [],
|
|
13
|
-
"https://i3.ytimg.com",
|
|
14
|
-
"https://www.youtube.com",
|
|
15
|
-
"https://www.youtube-nocookie.com",
|
|
16
|
-
...options.imgSrc || []
|
|
17
|
-
])),
|
|
19
|
+
"img-src": imgSources,
|
|
18
20
|
"font-src": ["'self'"],
|
|
19
21
|
"connect-src": Array.from(/* @__PURE__ */ new Set([
|
|
20
22
|
"'self'",
|
|
21
23
|
"https://cloudflareinsights.com",
|
|
24
|
+
...allowTurnstile ? ["https://challenges.cloudflare.com"] : [],
|
|
22
25
|
...options.connectSrc || []
|
|
23
26
|
])),
|
|
24
27
|
"frame-ancestors": ["'none'"],
|
|
25
28
|
"frame-src": Array.from(/* @__PURE__ */ new Set([
|
|
26
29
|
"https://www.youtube.com",
|
|
27
30
|
"https://www.youtube-nocookie.com",
|
|
31
|
+
...allowTurnstile ? ["https://challenges.cloudflare.com"] : [],
|
|
28
32
|
...options.frameSrc || []
|
|
29
33
|
])),
|
|
30
34
|
"script-src": Array.from(/* @__PURE__ */ new Set([
|
|
@@ -32,6 +36,7 @@ function buildCspHeader(options = {}, nonce) {
|
|
|
32
36
|
...nonce ? [`'nonce-${nonce}'`] : [],
|
|
33
37
|
"https://static.cloudflareinsights.com",
|
|
34
38
|
"https://betterlytics.io/analytics.js",
|
|
39
|
+
...allowTurnstile ? ["https://challenges.cloudflare.com"] : [],
|
|
35
40
|
"'inline-speculation-rules'",
|
|
36
41
|
"'sha256-ZuMSxilKU+4KIM8LWna4lqBEKzd6WaiAjz6gamhm7zM='",
|
|
37
42
|
...options.scriptResources || []
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "./types-
|
|
1
|
+
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "./types-CL5HooHU.mjs";
|
|
2
2
|
import { buildCspHeader } from "./csp.mjs";
|
|
3
3
|
import { RimelightSecurityPlugin, SecurityPluginOptions, security } from "./vite.mjs";
|
|
4
4
|
import { CONSTRUCTION_GUEST_COOKIE, construction, isConstructionGuest, signInConstructionGuest } from "./middleware/construction.mjs";
|
|
5
|
-
|
|
5
|
+
import rimelightSecurity, { RimelightSecurityOptions } from "./integration.mjs";
|
|
6
|
+
import { TurnstileVerifyOptions, TurnstileVerifyResult, getTurnstileSecretKey, getTurnstileSiteKey, isTurnstileEnabled, verifyTurnstile } from "./turnstile.mjs";
|
|
7
|
+
export { CONSTRUCTION_GUEST_COOKIE, ConstructionOptions, RateLimitOptions, RateLimiterBinding, RimelightSecurityOptions, RimelightSecurityPlugin, SecurityOptions, SecurityPluginOptions, TurnstileVerifyOptions, TurnstileVerifyResult, buildCspHeader, construction, getTurnstileSecretKey, getTurnstileSiteKey, isConstructionGuest, isTurnstileEnabled, rimelightSecurity, security, security as sri, signInConstructionGuest, verifyTurnstile };
|
package/dist/index.mjs
CHANGED
|
@@ -2,4 +2,6 @@ import "./types.mjs";
|
|
|
2
2
|
import { buildCspHeader } from "./csp.mjs";
|
|
3
3
|
import { security } from "./vite.mjs";
|
|
4
4
|
import { CONSTRUCTION_GUEST_COOKIE, construction, isConstructionGuest, signInConstructionGuest } from "./middleware/construction.mjs";
|
|
5
|
-
|
|
5
|
+
import rimelightSecurity from "./integration.mjs";
|
|
6
|
+
import { getTurnstileSecretKey, getTurnstileSiteKey, isTurnstileEnabled, verifyTurnstile } from "./turnstile.mjs";
|
|
7
|
+
export { CONSTRUCTION_GUEST_COOKIE, buildCspHeader, construction, getTurnstileSecretKey, getTurnstileSiteKey, isConstructionGuest, isTurnstileEnabled, rimelightSecurity, security, security as sri, signInConstructionGuest, verifyTurnstile };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { i as SecurityOptions, t as ConstructionOptions } from "./types-CL5HooHU.mjs";
|
|
2
|
+
import { AstroIntegration } from "astro";
|
|
3
|
+
//#region src/integration.d.ts
|
|
4
|
+
export interface RimelightSecurityOptions extends SecurityOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Construction mode options & route injection settings
|
|
7
|
+
*/
|
|
8
|
+
construction?: ConstructionOptions & {
|
|
9
|
+
/**
|
|
10
|
+
* Whether to automatically inject the construction page route. Defaults to true.
|
|
11
|
+
*/
|
|
12
|
+
injectRoute?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Pattern to inject for the construction route. Defaults to "/[locale]/construction" or
|
|
15
|
+
* "/construction".
|
|
16
|
+
*/
|
|
17
|
+
routePattern?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Astro Integration for Rimelight Security. Integrates Vite security plugins (CSP, SRI) and
|
|
22
|
+
* automatically injects the construction mode page route.
|
|
23
|
+
*/
|
|
24
|
+
export declare function rimelightSecurity(options?: RimelightSecurityOptions): AstroIntegration;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { rimelightSecurity as default };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { security } from "./vite.mjs";
|
|
2
|
+
//#region src/integration.ts
|
|
3
|
+
/**
|
|
4
|
+
* Astro Integration for Rimelight Security. Integrates Vite security plugins (CSP, SRI) and
|
|
5
|
+
* automatically injects the construction mode page route.
|
|
6
|
+
*/
|
|
7
|
+
function rimelightSecurity(options = {}) {
|
|
8
|
+
return {
|
|
9
|
+
name: "@rimelight/security",
|
|
10
|
+
hooks: { "astro:config:setup": ({ injectRoute, updateConfig }) => {
|
|
11
|
+
if (options.construction?.injectRoute !== false) injectRoute({
|
|
12
|
+
pattern: options.construction?.routePattern || "/[locale]/construction",
|
|
13
|
+
entrypoint: "@rimelight/security/pages/construction.astro"
|
|
14
|
+
});
|
|
15
|
+
updateConfig({ vite: { plugins: [security(options)] } });
|
|
16
|
+
} }
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { rimelightSecurity as default, rimelightSecurity };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as RateLimitOptions } from "../types-
|
|
1
|
+
import { n as RateLimitOptions } from "../types-CL5HooHU.mjs";
|
|
2
2
|
//#region src/middleware/ratelimit.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Middleware to enforce rate limits on sensitive endpoints using Cloudflare Rate Limiting bindings.
|
|
@@ -18,5 +18,5 @@ import { n as RateLimitOptions } from "../types-Dcvwj-af.mjs";
|
|
|
18
18
|
* })
|
|
19
19
|
* )
|
|
20
20
|
*/
|
|
21
|
-
export declare const ratelimit: (options?: RateLimitOptions) => (c: any, next: () => Promise<any>
|
|
21
|
+
export declare const ratelimit: (options?: RateLimitOptions) => (c: any, next: () => Promise<any>) => Promise<any>;
|
|
22
22
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as SecurityOptions } from "../types-
|
|
1
|
+
import { i as SecurityOptions } from "../types-CL5HooHU.mjs";
|
|
2
2
|
//#region src/middleware/security.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Universal Security Middleware for Hono and Web Standards (Fetch API). Injects security headers,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/turnstile.d.ts
|
|
2
|
+
export interface TurnstileVerifyOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The response token provided by the Turnstile client-side widget (e.g. `cf-turnstile-response`).
|
|
5
|
+
*/
|
|
6
|
+
token?: string | null | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* The secret key for Turnstile verification. Can be passed directly or read from `env`.
|
|
9
|
+
*/
|
|
10
|
+
secretKey?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The Cloudflare environment or process environment object containing TURNSTILE_SECRET_KEY /
|
|
13
|
+
* TURNSTILE_SITE_KEY.
|
|
14
|
+
*/
|
|
15
|
+
env?: any;
|
|
16
|
+
/**
|
|
17
|
+
* The client's IP address (e.g. from `CF-Connecting-IP` or `Astro.clientAddress`).
|
|
18
|
+
*/
|
|
19
|
+
remoteIp?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to fail open (succeed) if keys are not configured or during network errors. Defaults to
|
|
22
|
+
* `true` (safe for local development).
|
|
23
|
+
*/
|
|
24
|
+
failOpen?: boolean | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Custom verify endpoint URL. Defaults to
|
|
27
|
+
* `https://challenges.cloudflare.com/turnstile/v0/siteverify`.
|
|
28
|
+
*/
|
|
29
|
+
endpoint?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
export interface TurnstileVerifyResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
error?: string | undefined;
|
|
34
|
+
errorCodes?: string[] | undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Checks if Turnstile keys are present in the provided environment or `process.env`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTurnstileEnabled(env?: any): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the Turnstile secret key from options or environment.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getTurnstileSecretKey(options: TurnstileVerifyOptions): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the Turnstile site key from environment or process.env.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getTurnstileSiteKey(env?: any): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Verifies a Cloudflare Turnstile token on the server. Supports both options object and classic
|
|
50
|
+
* signature `(token, env, remoteIp)`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function verifyTurnstile(tokenOrOptions: string | null | undefined | TurnstileVerifyOptions, legacyEnv?: any, legacyRemoteIp?: string): Promise<TurnstileVerifyResult>;
|
|
53
|
+
//#endregion
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//#region src/turnstile.ts
|
|
2
|
+
/**
|
|
3
|
+
* Checks if Turnstile keys are present in the provided environment or `process.env`.
|
|
4
|
+
*/
|
|
5
|
+
function isTurnstileEnabled(env) {
|
|
6
|
+
const siteKey = env?.TURNSTILE_SITE_KEY || (typeof process !== "undefined" ? process.env?.["TURNSTILE_SITE_KEY"] : void 0);
|
|
7
|
+
const secretKey = env?.TURNSTILE_SECRET_KEY || (typeof process !== "undefined" ? process.env?.["TURNSTILE_SECRET_KEY"] : void 0);
|
|
8
|
+
return Boolean(siteKey && secretKey);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the Turnstile secret key from options or environment.
|
|
12
|
+
*/
|
|
13
|
+
function getTurnstileSecretKey(options) {
|
|
14
|
+
if (options.secretKey) return options.secretKey;
|
|
15
|
+
return options.env?.TURNSTILE_SECRET_KEY || (typeof process !== "undefined" ? process.env?.["TURNSTILE_SECRET_KEY"] : void 0);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the Turnstile site key from environment or process.env.
|
|
19
|
+
*/
|
|
20
|
+
function getTurnstileSiteKey(env) {
|
|
21
|
+
return env?.TURNSTILE_SITE_KEY || (typeof process !== "undefined" ? process.env?.["TURNSTILE_SITE_KEY"] : void 0);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Verifies a Cloudflare Turnstile token on the server. Supports both options object and classic
|
|
25
|
+
* signature `(token, env, remoteIp)`.
|
|
26
|
+
*/
|
|
27
|
+
async function verifyTurnstile(tokenOrOptions, legacyEnv, legacyRemoteIp) {
|
|
28
|
+
const options = typeof tokenOrOptions === "object" && tokenOrOptions !== null ? tokenOrOptions : {
|
|
29
|
+
token: tokenOrOptions,
|
|
30
|
+
env: legacyEnv,
|
|
31
|
+
remoteIp: legacyRemoteIp
|
|
32
|
+
};
|
|
33
|
+
const failOpen = options.failOpen ?? true;
|
|
34
|
+
const token = options.token;
|
|
35
|
+
const secretKey = getTurnstileSecretKey(options);
|
|
36
|
+
if (!secretKey) {
|
|
37
|
+
if (failOpen) {
|
|
38
|
+
console.log("[Turnstile] Keys missing or not configured. Skipping verification (Failing Open for local dev).");
|
|
39
|
+
return { success: true };
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: "Turnstile secret key is not configured."
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!token) return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: "Security check token is missing."
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
const endpoint = options.endpoint || "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
52
|
+
const response = await fetch(endpoint, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
secret: secretKey,
|
|
57
|
+
response: token,
|
|
58
|
+
remoteip: options.remoteIp
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
if (failOpen) {
|
|
63
|
+
console.error(`[Turnstile] API returned status ${response.status}. Failing open.`);
|
|
64
|
+
return { success: true };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: `Cloudflare Turnstile API returned status ${response.status}`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
if (data.success) return { success: true };
|
|
73
|
+
const codes = data["error-codes"] || [];
|
|
74
|
+
let msg = "Bot check verification failed.";
|
|
75
|
+
if (codes.includes("timeout-or-duplicate")) msg = "Verification token expired or already used. Please refresh the security check.";
|
|
76
|
+
else if (codes.includes("invalid-input-response")) msg = "Invalid verification response. Please try again.";
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: msg,
|
|
80
|
+
errorCodes: codes
|
|
81
|
+
};
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error("[Turnstile Exception]", err);
|
|
84
|
+
if (failOpen) return { success: true };
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: err?.message || "Turnstile verification encountered an exception."
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { getTurnstileSecretKey, getTurnstileSiteKey, isTurnstileEnabled, verifyTurnstile };
|
|
@@ -29,6 +29,11 @@ interface SecurityOptions {
|
|
|
29
29
|
* Additional style resources / origins
|
|
30
30
|
*/
|
|
31
31
|
styleResources?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Enables Cloudflare Turnstile CSP allowances (challenges.cloudflare.com in script-src,
|
|
34
|
+
* frame-src, connect-src). Defaults to true.
|
|
35
|
+
*/
|
|
36
|
+
turnstile?: boolean;
|
|
32
37
|
/**
|
|
33
38
|
* Enables the 'require-trusted-types-for' directive
|
|
34
39
|
*/
|
package/dist/types.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "./types-
|
|
1
|
+
import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t as ConstructionOptions } from "./types-CL5HooHU.mjs";
|
|
2
2
|
export { ConstructionOptions, RateLimitOptions, RateLimiterBinding, SecurityOptions };
|
package/dist/vite.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/security",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment's Security Package",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
-
"src
|
|
20
|
+
"src"
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
23
|
"exports": {
|
|
@@ -33,17 +33,30 @@
|
|
|
33
33
|
"types": "./dist/config/index.d.mts",
|
|
34
34
|
"import": "./dist/config/index.mjs"
|
|
35
35
|
},
|
|
36
|
+
"./integration": {
|
|
37
|
+
"types": "./dist/integration.d.mts",
|
|
38
|
+
"import": "./dist/integration.mjs"
|
|
39
|
+
},
|
|
40
|
+
"./turnstile": {
|
|
41
|
+
"types": "./dist/turnstile.d.mts",
|
|
42
|
+
"import": "./dist/turnstile.mjs"
|
|
43
|
+
},
|
|
44
|
+
"./components/*": "./src/components/*",
|
|
45
|
+
"./layouts/*": "./src/layouts/*",
|
|
46
|
+
"./pages/*": "./src/pages/*",
|
|
36
47
|
"./*": "./src/*"
|
|
37
48
|
},
|
|
38
49
|
"publishConfig": {
|
|
39
50
|
"access": "public"
|
|
40
51
|
},
|
|
41
52
|
"dependencies": {
|
|
53
|
+
"@rimelight/i18n": "0.0.17",
|
|
54
|
+
"@rimelight/seo": "0.0.14",
|
|
42
55
|
"@rimelight/ui": "0.0.57"
|
|
43
56
|
},
|
|
44
57
|
"devDependencies": {
|
|
45
58
|
"@astrojs/check": "0.9.10",
|
|
46
|
-
"@rimelight/config": "0.0.
|
|
59
|
+
"@rimelight/config": "0.0.17",
|
|
47
60
|
"astro": "7.3.3",
|
|
48
61
|
"hono": "4.13.8",
|
|
49
62
|
"typescript": "6.0.3"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ConstructionLayout from "../layouts/ConstructionLayout.astro";
|
|
3
|
+
import ConstructionSignIn from "./ConstructionSignIn.astro";
|
|
4
|
+
import RLALogo from "@rimelight/ui/components/logo/RLALogo.astro";
|
|
5
|
+
import RLALocaleSelector from "@rimelight/ui/components/locale-selector/RLALocaleSelector.astro";
|
|
6
|
+
import { t, getLocale } from "@rimelight/i18n";
|
|
7
|
+
import { siteConfig } from "virtual:rimelight/seo";
|
|
8
|
+
|
|
9
|
+
export interface LocaleOption {
|
|
10
|
+
code: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
href?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
title?: string | undefined;
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
metaTitle?: string | undefined;
|
|
19
|
+
metaDescription?: string | undefined;
|
|
20
|
+
noindex?: boolean | undefined;
|
|
21
|
+
favicon?: string | undefined;
|
|
22
|
+
|
|
23
|
+
// Branding & Logo
|
|
24
|
+
logoAlt?: string | undefined;
|
|
25
|
+
siteName?: string | undefined;
|
|
26
|
+
copyright?: string | undefined;
|
|
27
|
+
|
|
28
|
+
// Locales
|
|
29
|
+
locales?: (LocaleOption | string)[] | undefined;
|
|
30
|
+
currentLocale?: string | undefined;
|
|
31
|
+
showLocaleSelector?: boolean | undefined;
|
|
32
|
+
|
|
33
|
+
// Guest Passphrase Form
|
|
34
|
+
allowAuth?: boolean | undefined;
|
|
35
|
+
endpoint?: string | undefined;
|
|
36
|
+
passphraseLabel?: string | undefined;
|
|
37
|
+
passphrasePlaceholder?: string | undefined;
|
|
38
|
+
rememberMeLabel?: string | undefined;
|
|
39
|
+
signInButtonLabel?: string | undefined;
|
|
40
|
+
loadingButtonLabel?: string | undefined;
|
|
41
|
+
returnLaterText?: string | undefined;
|
|
42
|
+
|
|
43
|
+
// Layout & Styling
|
|
44
|
+
class?: string | undefined;
|
|
45
|
+
containerClass?: string | undefined;
|
|
46
|
+
bodyClass?: string | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
title,
|
|
51
|
+
description,
|
|
52
|
+
metaTitle,
|
|
53
|
+
metaDescription,
|
|
54
|
+
noindex = true,
|
|
55
|
+
favicon,
|
|
56
|
+
logoAlt,
|
|
57
|
+
siteName = (siteConfig as any)?.["name"] || "Rimelight Entertainment",
|
|
58
|
+
copyright,
|
|
59
|
+
locales = [
|
|
60
|
+
{ code: "en", label: "English" },
|
|
61
|
+
{ code: "pt", label: "Português" },
|
|
62
|
+
],
|
|
63
|
+
currentLocale,
|
|
64
|
+
showLocaleSelector = true,
|
|
65
|
+
allowAuth = true,
|
|
66
|
+
endpoint = "/api/construction-guest",
|
|
67
|
+
passphraseLabel,
|
|
68
|
+
passphrasePlaceholder,
|
|
69
|
+
rememberMeLabel,
|
|
70
|
+
signInButtonLabel,
|
|
71
|
+
loadingButtonLabel,
|
|
72
|
+
returnLaterText,
|
|
73
|
+
class: className = "flex min-h-screen flex-col items-center justify-center bg-black px-4 py-12",
|
|
74
|
+
containerClass = "z-10 w-full max-w-md space-y-8 text-center",
|
|
75
|
+
bodyClass = "isolate bg-black"
|
|
76
|
+
} = Astro.props;
|
|
77
|
+
|
|
78
|
+
// Safe i18n translation helper with fallback
|
|
79
|
+
const translate = (key: string, fallback: string): string => {
|
|
80
|
+
try {
|
|
81
|
+
const val = t(key);
|
|
82
|
+
if (val && val !== key) return val;
|
|
83
|
+
} catch {}
|
|
84
|
+
return fallback;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const resolvedMetaTitle = metaTitle ?? translate("page_construction.meta_title", title ?? "Under Construction");
|
|
88
|
+
const resolvedMetaDescription = metaDescription ?? translate("page_construction.meta_description", description ?? "This site is currently undergoing scheduled maintenance.");
|
|
89
|
+
const resolvedTitle = title ?? translate("page_construction.title", "Under Construction");
|
|
90
|
+
const resolvedDescription = description ?? translate("page_construction.description", "This site is currently undergoing scheduled maintenance and is restricted to authorized personnel.");
|
|
91
|
+
|
|
92
|
+
const resolvedPassphraseLabel = passphraseLabel ?? translate("page_construction.form_passphrase_label", "Passphrase");
|
|
93
|
+
const resolvedPassphrasePlaceholder = passphrasePlaceholder ?? translate("page_construction.form_passphrase_placeholder", "Enter passphrase...");
|
|
94
|
+
const resolvedRememberMeLabel = rememberMeLabel ?? translate("page_construction.form_remember_me_label", "Remember me");
|
|
95
|
+
const resolvedSignInButtonLabel = signInButtonLabel ?? translate("page_construction.form_sign_in_button", "Sign In");
|
|
96
|
+
const resolvedCopyright = copyright ?? `© ${new Date().getFullYear()} ${siteName}`;
|
|
97
|
+
|
|
98
|
+
let resolvedCurrentLocale = currentLocale;
|
|
99
|
+
if (!resolvedCurrentLocale) {
|
|
100
|
+
try {
|
|
101
|
+
resolvedCurrentLocale = getLocale();
|
|
102
|
+
} catch {
|
|
103
|
+
resolvedCurrentLocale = "en";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
<ConstructionLayout
|
|
109
|
+
title={resolvedMetaTitle}
|
|
110
|
+
description={resolvedMetaDescription}
|
|
111
|
+
favicon={favicon}
|
|
112
|
+
noindex={noindex}
|
|
113
|
+
bodyClass={bodyClass}
|
|
114
|
+
>
|
|
115
|
+
<slot name="head" slot="head" />
|
|
116
|
+
|
|
117
|
+
<div class={className}>
|
|
118
|
+
<div class={containerClass}>
|
|
119
|
+
<slot name="header">
|
|
120
|
+
<div class="flex flex-col items-center gap-4">
|
|
121
|
+
<slot name="logo">
|
|
122
|
+
<RLALogo class="h-12 w-auto text-white" variant="logomark" mode="white" alt={logoAlt || siteName} />
|
|
123
|
+
</slot>
|
|
124
|
+
<slot name="title">
|
|
125
|
+
<h1 class="m-0 text-3xl font-bold text-white sm:text-4xl">
|
|
126
|
+
{resolvedTitle}
|
|
127
|
+
</h1>
|
|
128
|
+
</slot>
|
|
129
|
+
<slot name="description">
|
|
130
|
+
<p class="m-0 text-neutral-400">
|
|
131
|
+
{resolvedDescription}
|
|
132
|
+
</p>
|
|
133
|
+
</slot>
|
|
134
|
+
</div>
|
|
135
|
+
</slot>
|
|
136
|
+
|
|
137
|
+
<slot name="header-extra" />
|
|
138
|
+
|
|
139
|
+
<slot name="form">
|
|
140
|
+
<ConstructionSignIn
|
|
141
|
+
allowAuth={allowAuth}
|
|
142
|
+
endpoint={endpoint}
|
|
143
|
+
passphraseLabel={resolvedPassphraseLabel}
|
|
144
|
+
passphrasePlaceholder={resolvedPassphrasePlaceholder}
|
|
145
|
+
rememberMeLabel={resolvedRememberMeLabel}
|
|
146
|
+
signInButtonLabel={resolvedSignInButtonLabel}
|
|
147
|
+
loadingButtonLabel={loadingButtonLabel}
|
|
148
|
+
returnLaterText={returnLaterText}
|
|
149
|
+
/>
|
|
150
|
+
</slot>
|
|
151
|
+
|
|
152
|
+
<slot name="footer">
|
|
153
|
+
<div class="flex flex-col items-center gap-2 text-xs text-neutral-500">
|
|
154
|
+
<slot name="locale-selector">
|
|
155
|
+
{showLocaleSelector && locales && locales.length > 1 && (
|
|
156
|
+
<RLALocaleSelector locales={locales as any} currentLocale={resolvedCurrentLocale} />
|
|
157
|
+
)}
|
|
158
|
+
</slot>
|
|
159
|
+
<slot name="copyright">
|
|
160
|
+
<span>{resolvedCopyright}</span>
|
|
161
|
+
</slot>
|
|
162
|
+
</div>
|
|
163
|
+
</slot>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</ConstructionLayout>
|
|
@@ -6,15 +6,15 @@ import RLAButton from "@rimelight/ui/components/button/RLAButton.astro"
|
|
|
6
6
|
import RLACheckbox from "@rimelight/ui/components/checkbox/RLACheckbox.astro"
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
allowAuth?: boolean
|
|
10
|
-
endpoint?: string
|
|
11
|
-
passphraseLabel?: string
|
|
12
|
-
passphrasePlaceholder?: string
|
|
13
|
-
rememberMeLabel?: string
|
|
14
|
-
signInButtonLabel?: string
|
|
15
|
-
loadingButtonLabel?: string
|
|
16
|
-
returnLaterText?: string
|
|
17
|
-
class?: string
|
|
9
|
+
allowAuth?: boolean | undefined
|
|
10
|
+
endpoint?: string | undefined
|
|
11
|
+
passphraseLabel?: string | undefined
|
|
12
|
+
passphrasePlaceholder?: string | undefined
|
|
13
|
+
rememberMeLabel?: string | undefined
|
|
14
|
+
signInButtonLabel?: string | undefined
|
|
15
|
+
loadingButtonLabel?: string | undefined
|
|
16
|
+
returnLaterText?: string | undefined
|
|
17
|
+
class?: string | undefined
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getTurnstileSiteKey } from "../turnstile"
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
/**
|
|
6
|
+
* Cloudflare Turnstile Site Key. If omitted, attempts to read from `Astro.locals.runtime.env` or `import.meta.env`.
|
|
7
|
+
*/
|
|
8
|
+
siteKey?: string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Theme mode for Turnstile widget: "auto", "light", or "dark". Defaults to "auto".
|
|
12
|
+
*/
|
|
13
|
+
theme?: "auto" | "light" | "dark"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Widget size: "normal", "compact", or "flexible". Defaults to "normal".
|
|
17
|
+
*/
|
|
18
|
+
size?: "normal" | "compact" | "flexible"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Optional custom CSS class.
|
|
22
|
+
*/
|
|
23
|
+
class?: string
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Form field name for the generated token. Defaults to "cf-turnstile-response".
|
|
27
|
+
*/
|
|
28
|
+
responseFieldName?: string
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Action name used for Turnstile analytics / reporting.
|
|
32
|
+
*/
|
|
33
|
+
action?: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* CData payload if using custom challenge metadata.
|
|
37
|
+
*/
|
|
38
|
+
cdata?: string
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Turnstile execution mode: "render" (immediate) or "execute" (explicit).
|
|
42
|
+
*/
|
|
43
|
+
execution?: "render" | "execute"
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Turnstile appearance mode: "always", "execute", or "interaction-only".
|
|
47
|
+
*/
|
|
48
|
+
appearance?: "always" | "execute" | "interaction-only"
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Automatically load the Cloudflare Turnstile api.js script. Defaults to true.
|
|
52
|
+
*/
|
|
53
|
+
injectScript?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
siteKey: propSiteKey,
|
|
58
|
+
theme = "auto",
|
|
59
|
+
size = "normal",
|
|
60
|
+
class: className = "cf-turnstile my-2",
|
|
61
|
+
responseFieldName = "cf-turnstile-response",
|
|
62
|
+
action,
|
|
63
|
+
cdata,
|
|
64
|
+
execution,
|
|
65
|
+
appearance,
|
|
66
|
+
injectScript = true,
|
|
67
|
+
...rest
|
|
68
|
+
} = Astro.props
|
|
69
|
+
|
|
70
|
+
const env = (Astro.locals as any)?.runtime?.env
|
|
71
|
+
const resolvedSiteKey = propSiteKey || getTurnstileSiteKey(env) || import.meta.env["TURNSTILE_SITE_KEY"] || ""
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
resolvedSiteKey && (
|
|
76
|
+
<>
|
|
77
|
+
<div
|
|
78
|
+
class={className}
|
|
79
|
+
data-sitekey={resolvedSiteKey}
|
|
80
|
+
data-theme={theme}
|
|
81
|
+
data-size={size}
|
|
82
|
+
data-response-field-name={responseFieldName}
|
|
83
|
+
data-action={action}
|
|
84
|
+
data-cdata={cdata}
|
|
85
|
+
data-execution={execution}
|
|
86
|
+
data-appearance={appearance}
|
|
87
|
+
{...rest}
|
|
88
|
+
/>
|
|
89
|
+
{injectScript && (
|
|
90
|
+
<script is:inline src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)
|
|
94
|
+
}
|