@rimelight/security 0.0.18 → 0.0.20

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/index.d.mts CHANGED
@@ -2,4 +2,5 @@ import { i as SecurityOptions, n as RateLimitOptions, r as RateLimiterBinding, t
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
- export { CONSTRUCTION_GUEST_COOKIE, ConstructionOptions, RateLimitOptions, RateLimiterBinding, RimelightSecurityPlugin, SecurityOptions, SecurityPluginOptions, buildCspHeader, construction, isConstructionGuest, security, security as sri, signInConstructionGuest };
5
+ import rimelightSecurity, { RimelightSecurityOptions } from "./integration.mjs";
6
+ export { CONSTRUCTION_GUEST_COOKIE, ConstructionOptions, RateLimitOptions, RateLimiterBinding, RimelightSecurityOptions, RimelightSecurityPlugin, SecurityOptions, SecurityPluginOptions, buildCspHeader, construction, isConstructionGuest, rimelightSecurity, security, security as sri, signInConstructionGuest };
package/dist/index.mjs CHANGED
@@ -2,4 +2,5 @@ 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
- export { CONSTRUCTION_GUEST_COOKIE, buildCspHeader, construction, isConstructionGuest, security, security as sri, signInConstructionGuest };
5
+ import rimelightSecurity from "./integration.mjs";
6
+ export { CONSTRUCTION_GUEST_COOKIE, buildCspHeader, construction, isConstructionGuest, rimelightSecurity, security, security as sri, signInConstructionGuest };
@@ -0,0 +1,26 @@
1
+ import { i as SecurityOptions, t as ConstructionOptions } from "./types-Dcvwj-af.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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/security",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
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/components"
20
+ "src"
21
21
  ],
22
22
  "type": "module",
23
23
  "exports": {
@@ -33,17 +33,26 @@
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
+ "./components/*": "./src/components/*",
41
+ "./layouts/*": "./src/layouts/*",
42
+ "./pages/*": "./src/pages/*",
36
43
  "./*": "./src/*"
37
44
  },
38
45
  "publishConfig": {
39
46
  "access": "public"
40
47
  },
41
48
  "dependencies": {
42
- "@rimelight/ui": "0.0.56"
49
+ "@rimelight/i18n": "0.0.17",
50
+ "@rimelight/seo": "0.0.13",
51
+ "@rimelight/ui": "0.0.57"
43
52
  },
44
53
  "devDependencies": {
45
54
  "@astrojs/check": "0.9.10",
46
- "@rimelight/config": "0.0.13",
55
+ "@rimelight/config": "0.0.16",
47
56
  "astro": "7.3.3",
48
57
  "hono": "4.13.8",
49
58
  "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,2 @@
1
+ export * from "../types"
2
+ export * from "../csp"
package/src/csp.ts ADDED
@@ -0,0 +1,97 @@
1
+ import type { SecurityOptions } from "./types"
2
+
3
+ /**
4
+ * Builds the Content-Security-Policy header string dynamically for a given request nonce.
5
+ */
6
+ export function buildCspHeader(options: SecurityOptions = {}, nonce?: string): string {
7
+ const domain = options.domain || ""
8
+
9
+ const imgSources = Array.from(
10
+ new Set([
11
+ "'self'",
12
+ "data:",
13
+ ...(domain ? [`https://cdn.${domain}`] : []),
14
+ "https://i3.ytimg.com",
15
+ "https://www.youtube.com",
16
+ "https://www.youtube-nocookie.com",
17
+ ...(options.imgSrc || [])
18
+ ])
19
+ )
20
+
21
+ const connectSources = Array.from(
22
+ new Set(["'self'", "https://cloudflareinsights.com", ...(options.connectSrc || [])])
23
+ )
24
+
25
+ const frameSources = Array.from(
26
+ new Set([
27
+ "https://www.youtube.com",
28
+ "https://www.youtube-nocookie.com",
29
+ ...(options.frameSrc || [])
30
+ ])
31
+ )
32
+
33
+ const scriptSources = Array.from(
34
+ new Set([
35
+ "'self'",
36
+ ...(nonce ? [`'nonce-${nonce}'`] : []),
37
+ "https://static.cloudflareinsights.com",
38
+ "https://betterlytics.io/analytics.js",
39
+ "'inline-speculation-rules'",
40
+ // Astro ClientRouter (View Transitions inline script sha)
41
+ "'sha256-ZuMSxilKU+4KIM8LWna4lqBEKzd6WaiAjz6gamhm7zM='",
42
+ ...(options.scriptResources || [])
43
+ ])
44
+ )
45
+
46
+ const styleSources = Array.from(
47
+ new Set([
48
+ "'self'",
49
+ // Astro ClientRouter (View Transitions inline style sha)
50
+ "'sha256-SKuaOGnks7NAUq37nvw1PfGEE0lOAs5ERbiYRbGFIbw='",
51
+ ...(options.styleResources || [])
52
+ ])
53
+ )
54
+
55
+ const defaultDirectives: Record<string, string[]> = {
56
+ "default-src": ["'none'"],
57
+ "img-src": imgSources,
58
+ "font-src": ["'self'"],
59
+ "connect-src": connectSources,
60
+ "frame-ancestors": ["'none'"],
61
+ "frame-src": frameSources,
62
+ "script-src": scriptSources,
63
+ "script-src-attr": ["'none'"],
64
+ "style-src": styleSources,
65
+ "base-uri": ["'self'"],
66
+ "form-action": ["'self'"],
67
+ "object-src": ["'none'"],
68
+ "manifest-src": ["'self'"],
69
+ "upgrade-insecure-requests": []
70
+ }
71
+
72
+ if (options.trustedTypes) {
73
+ defaultDirectives["require-trusted-types-for"] = ["'script'"]
74
+ }
75
+
76
+ // Merge custom directive overrides
77
+ if (options.directives) {
78
+ for (const [key, value] of Object.entries(options.directives)) {
79
+ if (value === false) {
80
+ delete defaultDirectives[key]
81
+ } else if (value === true) {
82
+ defaultDirectives[key] = []
83
+ } else if (Array.isArray(value)) {
84
+ defaultDirectives[key] = value
85
+ } else if (typeof value === "string") {
86
+ defaultDirectives[key] = value.split(" ").filter(Boolean)
87
+ }
88
+ }
89
+ }
90
+
91
+ return Object.entries(defaultDirectives)
92
+ .map(([directive, values]) => {
93
+ if (!values || values.length === 0) return directive
94
+ return `${directive} ${values.join(" ")}`
95
+ })
96
+ .join("; ")
97
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./vite"
2
+ export * from "./types"
3
+ export * from "./csp"
4
+ export * from "./middleware/construction"
5
+ export * from "./integration"
@@ -0,0 +1,51 @@
1
+ import type { AstroIntegration } from "astro"
2
+ import type { SecurityOptions, ConstructionOptions } from "./types"
3
+ import { security } from "./vite"
4
+
5
+ export interface RimelightSecurityOptions extends SecurityOptions {
6
+ /**
7
+ * Construction mode options & route injection settings
8
+ */
9
+ construction?: ConstructionOptions & {
10
+ /**
11
+ * Whether to automatically inject the construction page route. Defaults to true.
12
+ */
13
+ injectRoute?: boolean
14
+ /**
15
+ * Pattern to inject for the construction route. Defaults to "/[locale]/construction" or
16
+ * "/construction".
17
+ */
18
+ routePattern?: string
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Astro Integration for Rimelight Security. Integrates Vite security plugins (CSP, SRI) and
24
+ * automatically injects the construction mode page route.
25
+ */
26
+ export function rimelightSecurity(options: RimelightSecurityOptions = {}): AstroIntegration {
27
+ return {
28
+ name: "@rimelight/security",
29
+ hooks: {
30
+ "astro:config:setup": ({ injectRoute, updateConfig }) => {
31
+ // 1. Inject Construction Page Route by default (unless explicitly disabled)
32
+ if (options.construction?.injectRoute !== false) {
33
+ const pattern = options.construction?.routePattern || "/[locale]/construction"
34
+ injectRoute({
35
+ pattern,
36
+ entrypoint: "@rimelight/security/pages/construction.astro"
37
+ })
38
+ }
39
+
40
+ // 2. Add Vite security plugin
41
+ updateConfig({
42
+ vite: {
43
+ plugins: [security(options)]
44
+ }
45
+ })
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export default rimelightSecurity
@@ -0,0 +1,50 @@
1
+ ---
2
+ import SecurityHead from "../components/SecurityHead.astro";
3
+ import SEOHead from "@rimelight/seo/components/SEOHead.astro";
4
+ import UIHead from "@rimelight/ui/components/head/UIHead.astro";
5
+ import RLAMain from "@rimelight/ui/components/main/RLAMain.astro";
6
+ import { siteConfig } from "virtual:rimelight/seo";
7
+ import { Font } from "astro:assets";
8
+
9
+ export interface Props {
10
+ title?: string | undefined;
11
+ description?: string | undefined;
12
+ favicon?: string | undefined;
13
+ noindex?: boolean | undefined;
14
+ class?: string | undefined;
15
+ bodyClass?: string | undefined;
16
+ }
17
+
18
+ const {
19
+ title = "Under Construction",
20
+ description = "This site is currently undergoing scheduled construction.",
21
+ favicon,
22
+ noindex = true,
23
+ class: className = "",
24
+ bodyClass = "isolate bg-black"
25
+ } = Astro.props;
26
+ ---
27
+
28
+ <!doctype html>
29
+ <html lang="en">
30
+ <head>
31
+ <SecurityHead />
32
+ <SEOHead
33
+ title={title}
34
+ description={description}
35
+ config={siteConfig}
36
+ favicon={favicon}
37
+ noindex={noindex}
38
+ />
39
+ <UIHead />
40
+ <Font cssVariable="--font-sans" preload={true} />
41
+ <Font cssVariable="--font-serif" preload={true} />
42
+ <Font cssVariable="--font-mono" preload={true} />
43
+ <slot name="head" />
44
+ </head>
45
+ <body class={bodyClass}>
46
+ <RLAMain class={className}>
47
+ <slot />
48
+ </RLAMain>
49
+ </body>
50
+ </html>