@functionalcms/svelte-components 5.15.0 → 5.16.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.
@@ -10,3 +10,4 @@ export type { RedirectResponse } from './server-side/auth/RedirectResponse.js';
10
10
  export { isHuman } from './components/form/AntiBot.js';
11
11
  export { paraglideHandler } from './server-side/handlers/paraglideHandler.js';
12
12
  export { redirectPipelineHandler } from './server-side/handlers/redirectPipelineHandler.js';
13
+ export { saveCookie, loadCookie } from './server-side/cookies.js';
@@ -9,3 +9,4 @@ export { getCommunicationService, getDataService, getTemplateService, getWebsite
9
9
  export { isHuman } from './components/form/AntiBot.js';
10
10
  export { paraglideHandler } from './server-side/handlers/paraglideHandler.js';
11
11
  export { redirectPipelineHandler } from './server-side/handlers/redirectPipelineHandler.js';
12
+ export { saveCookie, loadCookie } from './server-side/cookies.js';
@@ -0,0 +1,89 @@
1
+ import { CACHE_TOKEN, CACHE_URL, CLIENT_ID, SERVER_URL, CACHE_SECRET } from '$env/static/private';
2
+ import { type RequestHandler } from '@sveltejs/kit';
3
+ import { Redis } from '@upstash/redis';
4
+ import * as o from "oauth4webapi";
5
+ import { saveCookie } from '@functionalcms/svelte-components/index-server.js';
6
+ import { loadCookie } from '../../../lib/index-server';
7
+
8
+ const pkceCookieName = "functionalcms_pkce";
9
+ const stateCookieName = "functionalcms_state";
10
+ const sessionCookieName = "functionalcms_sessionId";
11
+
12
+ function parseJwtToken(token: string) {
13
+ const base64 = token.split('.')[1]
14
+ .replace(/-/g, '+')
15
+ .replace(/_/g, '/');
16
+
17
+ const json = decodeURIComponent(
18
+ atob(base64)
19
+ .split('')
20
+ .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
21
+ .join('')
22
+ );
23
+
24
+ return JSON.parse(json);
25
+ }
26
+
27
+ async function handleCallback (state: string, codeVerifier: string, callbackUrl: URL): Promise<any> {
28
+ const discovery = await o.discoveryRequest(new URL(SERVER_URL));
29
+ const metadata = await o.processDiscoveryResponse(new URL(SERVER_URL), discovery);
30
+ const client: o.Client = {
31
+ client_id: CLIENT_ID,
32
+ token_endpoint_auth_method: "none"
33
+ };
34
+ const callbackParameters
35
+ = o.validateAuthResponse(
36
+ metadata,
37
+ client,
38
+ callbackUrl,
39
+ state);
40
+
41
+ // Exchange code for tokens
42
+ const tokenResponse
43
+ = await o.authorizationCodeGrantRequest(
44
+ metadata,
45
+ client,
46
+ o.None(),
47
+ callbackParameters,
48
+ callbackUrl.toString(),
49
+ codeVerifier,
50
+ );
51
+
52
+ // Parse tokens
53
+ const tokens
54
+ = await o.processAuthorizationCodeResponse(
55
+ metadata,
56
+ client,
57
+ tokenResponse
58
+ );
59
+
60
+ return {
61
+ access_token: tokens.access_token,
62
+ id_token: tokens.id_token,
63
+ refresh_token: tokens.refresh_token,
64
+ };
65
+ };
66
+
67
+
68
+ /** @type {import('./$types').RequestHandler} */
69
+ export const GET: RequestHandler = async ({ url, cookies }) => {
70
+ const expectedState = await loadCookie(cookies, CACHE_SECRET, stateCookieName);
71
+ const codeVerifier = await loadCookie(cookies, CACHE_SECRET, pkceCookieName);
72
+
73
+ const response = await handleCallback(expectedState ?? '', codeVerifier ?? '', new URL(url));
74
+
75
+ const headers = new Headers();
76
+ headers.set('Location', url.origin);
77
+ const sessionId = crypto.randomUUID();
78
+ const accessToken = parseJwtToken(response.access_token);
79
+
80
+ await saveCookie(headers, CACHE_SECRET, sessionCookieName, sessionId);
81
+
82
+ const redis = new Redis({ url: CACHE_URL, token: CACHE_TOKEN });
83
+ const result = await redis.set(sessionId, JSON.stringify(response.access_token), { ex: accessToken.exp - Math.floor(Date.now() / 1000) });
84
+
85
+ cookies.delete(pkceCookieName, { path: "/" });
86
+ cookies.delete(stateCookieName, { path: "/" });
87
+
88
+ return new Response(null, { status: 302, headers });
89
+ }
@@ -0,0 +1,45 @@
1
+ import { CLIENT_ID, SERVER_URL, CACHE_SECRET } from '$env/static/private';
2
+ import { type RequestHandler } from '@sveltejs/kit';
3
+ import * as o from "oauth4webapi";
4
+ import { saveCookie } from '@functionalcms/svelte-components/index-server.js';
5
+
6
+ const pkceCookieName = "functionalcms_pkce";
7
+ const stateCookieName = "functionalcms_state";
8
+
9
+ function generateRandomState() {
10
+ const array = new Uint8Array(16);
11
+ crypto.getRandomValues(array);
12
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
13
+ }
14
+
15
+ async function createLoginHeader(serverUrl: string, clientId: string, callbackUrl: URL) {
16
+ const code_verifier = o.generateRandomCodeVerifier();
17
+ const code_challenge = await o.calculatePKCECodeChallenge(code_verifier);
18
+ const state = generateRandomState();
19
+
20
+ const authUrl = new URL(serverUrl + '/login/oauth/authorize');
21
+ authUrl.searchParams.set('client_id', clientId);
22
+ authUrl.searchParams.set('redirect_uri', callbackUrl.toString());
23
+ authUrl.searchParams.set('response_type', 'code');
24
+ authUrl.searchParams.set('state', state);
25
+ authUrl.searchParams.set('code_challenge', code_challenge);
26
+ authUrl.searchParams.set('code_challenge_method', 'S256');
27
+ authUrl.searchParams.set('scope', 'openid profile email user.extended properties');
28
+
29
+ return { state, codeVerifier: code_verifier, authorisationUrl: authUrl.toString() };
30
+ }
31
+
32
+ /** @type {import('./$types').RequestHandler} */
33
+ export const GET: RequestHandler = async ({ params, locals, url }) => {
34
+ const callbackUrl = new URL(`${url.origin}/auth/callback`);
35
+ const response = await createLoginHeader(SERVER_URL, CLIENT_ID, callbackUrl);
36
+
37
+ const headers = new Headers();
38
+ headers.set('Location', response.authorisationUrl);
39
+
40
+ await saveCookie(headers, CACHE_SECRET, pkceCookieName, response.codeVerifier);
41
+ await saveCookie(headers, CACHE_SECRET, stateCookieName, response.state);
42
+
43
+
44
+ return new Response(null, { status: 302, headers });
45
+ }
@@ -1,12 +1,10 @@
1
1
  import { deleteCookie, loadCookie, saveCookie } from "../cookies";
2
2
  import { userAuthenticationProvider } from "./userAuthenticationProvider";
3
- import { parse } from "svelte/compiler";
4
3
  import { Redis } from "@upstash/redis";
4
+ import { parseJwtToken } from "../tokenToUserParser";
5
5
  const pkceCookieName = "functionalcms_pkce";
6
6
  const stateCookieName = "functionalcms_state";
7
7
  const sessionCookieName = "functionalcms_sessionId";
8
- const idTokenCookie = "functionalcms_id_token";
9
- const refreshTokenCookie = "functionalcms_refresh_token";
10
8
  export const userAuthenticationHandle = (secret, serverUrl, clientId, redis) => {
11
9
  const provider = userAuthenticationProvider(serverUrl, clientId);
12
10
  return async ({ event, resolve }) => {
@@ -36,16 +34,6 @@ function handleLogout(event) {
36
34
  event.locals.user = null;
37
35
  event.locals.session = {};
38
36
  }
39
- function parseJwt(token) {
40
- const base64 = token.split('.')[1]
41
- .replace(/-/g, '+')
42
- .replace(/_/g, '/');
43
- const json = decodeURIComponent(atob(base64)
44
- .split('')
45
- .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
46
- .join(''));
47
- return JSON.parse(json);
48
- }
49
37
  async function handleLogin(event, provider, secret) {
50
38
  const callbackUrl = new URL(`${event.url.origin}/auth/callback`);
51
39
  const response = await provider.handleLogin(callbackUrl);
@@ -62,16 +50,9 @@ async function handleCallback(event, secret, provider, redis) {
62
50
  const headers = new Headers();
63
51
  headers.set('Location', event.url.origin);
64
52
  const sessionId = crypto.randomUUID();
65
- const accessToken = parseJwt(response.access_token);
53
+ const accessToken = parseJwtToken(response.access_token);
66
54
  await saveCookie(headers, secret, sessionCookieName, sessionId, accessToken.exp - Math.floor(Date.now() / 1000));
67
55
  redis.set(sessionId, JSON.stringify(response.access_token), { ex: accessToken.exp - Math.floor(Date.now() / 1000) });
68
- // await saveCookie(headers, secret, accessTokenCookie, response.access_token, accessToken.exp - Math.floor(Date.now() / 1000));
69
- // if (response.id_token) {
70
- // await saveCookie(headers, secret, idTokenCookie, response.id_token);
71
- // }
72
- // if (response.refresh_token) {
73
- // await saveCookie(headers, secret, refreshTokenCookie, response.refresh_token);
74
- // }
75
56
  await deleteCookie(event.cookies, headers, pkceCookieName);
76
57
  await deleteCookie(event.cookies, headers, stateCookieName);
77
58
  return headers;
@@ -6,5 +6,5 @@ import { userHandler } from "./handlers/userHandler";
6
6
  import { accessTokenHandler } from "./handlers/accessTokenHandler";
7
7
  import { sitemapHandle } from "./handlers/sitemapHandler";
8
8
  export const hookSequence = (secret, redis, paraglideMiddleware, getTextDirection, settings, customHooks) => {
9
- return sequence(userHandler(secret, redis), accessTokenHandler(secret), paraglideHandler(paraglideMiddleware, getTextDirection), authorizationHandle(), sitemapHandle(settings), ...customHooks, errorHandler);
9
+ return sequence(accessTokenHandler(secret, redis), userHandler(), paraglideHandler(paraglideMiddleware, getTextDirection), authorizationHandle(), sitemapHandle(settings), ...customHooks, errorHandler);
10
10
  };
@@ -1,2 +1,3 @@
1
1
  import { type Handle } from '@sveltejs/kit';
2
- export declare const accessTokenHandler: (secret: string) => Handle;
2
+ import { Redis } from '@upstash/redis';
3
+ export declare const accessTokenHandler: (secret: string, redis: Redis) => Handle;
@@ -1,12 +1,14 @@
1
1
  import {} from '@sveltejs/kit';
2
2
  import { loadCookie } from '../cookies';
3
+ import { Redis } from '@upstash/redis';
3
4
  const accessTokenCookie = "functionalcms_sessionId";
4
- export const accessTokenHandler = (secret) => {
5
+ export const accessTokenHandler = (secret, redis) => {
5
6
  return async ({ event, resolve }) => {
6
7
  try {
7
- let token = await loadCookie(event.cookies, secret, accessTokenCookie);
8
- if (token) {
9
- event.locals.token = token;
8
+ let sessionId = await loadCookie(event.cookies, secret, accessTokenCookie);
9
+ if (sessionId) {
10
+ const accessToken = await redis.get(sessionId);
11
+ event.locals.token = accessToken;
10
12
  }
11
13
  }
12
14
  finally {
@@ -1,3 +1,2 @@
1
1
  import { type Handle } from '@sveltejs/kit';
2
- import { Redis } from '@upstash/redis';
3
- export declare const userHandler: (secret: string, redis: Redis) => Handle;
2
+ export declare const userHandler: () => Handle;
@@ -1,13 +1,13 @@
1
1
  import {} from '@sveltejs/kit';
2
2
  import { loadCookie } from '../cookies';
3
3
  import { Redis } from '@upstash/redis';
4
+ import { parseJwtToken } from '../tokenToUserParser';
4
5
  const accessTokenCookie = "functionalcms_sessionId";
5
- export const userHandler = (secret, redis) => {
6
+ export const userHandler = () => {
6
7
  return async ({ event, resolve }) => {
7
8
  try {
8
- let sessionId = await loadCookie(event.cookies, secret, accessTokenCookie);
9
- if (sessionId) {
10
- const user = await redis.get(sessionId);
9
+ if (event.locals.token) {
10
+ const user = parseJwtToken(event.locals.token);
11
11
  event.locals.user = user;
12
12
  }
13
13
  }
@@ -0,0 +1 @@
1
+ export declare function parseJwtToken(token: string): any;
@@ -0,0 +1,10 @@
1
+ export function parseJwtToken(token) {
2
+ const base64 = token.split('.')[1]
3
+ .replace(/-/g, '+')
4
+ .replace(/_/g, '/');
5
+ const json = decodeURIComponent(atob(base64)
6
+ .split('')
7
+ .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
8
+ .join(''));
9
+ return JSON.parse(json);
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@functionalcms/svelte-components",
3
- "version": "5.15.0",
3
+ "version": "5.16.0",
4
4
  "license": "MIT",
5
5
  "watch": {
6
6
  "build": {
@@ -14,9 +14,11 @@
14
14
  "scripts": {
15
15
  "dev": "vite dev",
16
16
  "build-css": "node -e \"const fs = require('node:fs'); fs.mkdirSync('./css', { recursive: true }); fs.copyFileSync('./src/css/functional.css', './css/functional.css');\"",
17
+ "build-modules": "node -e \"require('node:fs').cpSync('./src/pages', './dist/pages', { recursive: true })\"",
17
18
  "build": "vite build && npm run package",
18
19
  "preview": "vite preview",
19
- "package": "npm run build-css && svelte-kit sync && svelte-package && publint",
20
+ "package": "npm run build-css && svelte-kit sync && svelte-package && npm run build-modules && publint",
21
+ "prepack": "npm run package",
20
22
  "prepublishOnly": "npm run package",
21
23
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
22
24
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
@@ -37,6 +39,7 @@
37
39
  "files": [
38
40
  "dist",
39
41
  "css",
42
+ "src/pages",
40
43
  "!dist/**/*.test.*",
41
44
  "!dist/**/*.spec.*"
42
45
  ],
@@ -46,6 +49,7 @@
46
49
  "@sveltejs/kit": "^2.69.2",
47
50
  "@sveltejs/package": "^2.5.8",
48
51
  "@sveltejs/vite-plugin-svelte": "^7.2.0",
52
+ "cpy-cli": "^7.0.0",
49
53
  "publint": "^0.3.21",
50
54
  "svelte": "^5.56.4",
51
55
  "svelte-check": "^4.7.2",
@@ -72,4 +76,4 @@
72
76
  "main": "./dist/index.js",
73
77
  "svelte": "./dist/index.js",
74
78
  "types": "./dist/index.d.ts"
75
- }
79
+ }
@@ -0,0 +1,89 @@
1
+ import { CACHE_TOKEN, CACHE_URL, CLIENT_ID, SERVER_URL, CACHE_SECRET } from '$env/static/private';
2
+ import { type RequestHandler } from '@sveltejs/kit';
3
+ import { Redis } from '@upstash/redis';
4
+ import * as o from "oauth4webapi";
5
+ import { saveCookie } from '@functionalcms/svelte-components/index-server.js';
6
+ import { loadCookie } from '../../../lib/index-server';
7
+
8
+ const pkceCookieName = "functionalcms_pkce";
9
+ const stateCookieName = "functionalcms_state";
10
+ const sessionCookieName = "functionalcms_sessionId";
11
+
12
+ function parseJwtToken(token: string) {
13
+ const base64 = token.split('.')[1]
14
+ .replace(/-/g, '+')
15
+ .replace(/_/g, '/');
16
+
17
+ const json = decodeURIComponent(
18
+ atob(base64)
19
+ .split('')
20
+ .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
21
+ .join('')
22
+ );
23
+
24
+ return JSON.parse(json);
25
+ }
26
+
27
+ async function handleCallback (state: string, codeVerifier: string, callbackUrl: URL): Promise<any> {
28
+ const discovery = await o.discoveryRequest(new URL(SERVER_URL));
29
+ const metadata = await o.processDiscoveryResponse(new URL(SERVER_URL), discovery);
30
+ const client: o.Client = {
31
+ client_id: CLIENT_ID,
32
+ token_endpoint_auth_method: "none"
33
+ };
34
+ const callbackParameters
35
+ = o.validateAuthResponse(
36
+ metadata,
37
+ client,
38
+ callbackUrl,
39
+ state);
40
+
41
+ // Exchange code for tokens
42
+ const tokenResponse
43
+ = await o.authorizationCodeGrantRequest(
44
+ metadata,
45
+ client,
46
+ o.None(),
47
+ callbackParameters,
48
+ callbackUrl.toString(),
49
+ codeVerifier,
50
+ );
51
+
52
+ // Parse tokens
53
+ const tokens
54
+ = await o.processAuthorizationCodeResponse(
55
+ metadata,
56
+ client,
57
+ tokenResponse
58
+ );
59
+
60
+ return {
61
+ access_token: tokens.access_token,
62
+ id_token: tokens.id_token,
63
+ refresh_token: tokens.refresh_token,
64
+ };
65
+ };
66
+
67
+
68
+ /** @type {import('./$types').RequestHandler} */
69
+ export const GET: RequestHandler = async ({ url, cookies }) => {
70
+ const expectedState = await loadCookie(cookies, CACHE_SECRET, stateCookieName);
71
+ const codeVerifier = await loadCookie(cookies, CACHE_SECRET, pkceCookieName);
72
+
73
+ const response = await handleCallback(expectedState ?? '', codeVerifier ?? '', new URL(url));
74
+
75
+ const headers = new Headers();
76
+ headers.set('Location', url.origin);
77
+ const sessionId = crypto.randomUUID();
78
+ const accessToken = parseJwtToken(response.access_token);
79
+
80
+ await saveCookie(headers, CACHE_SECRET, sessionCookieName, sessionId);
81
+
82
+ const redis = new Redis({ url: CACHE_URL, token: CACHE_TOKEN });
83
+ const result = await redis.set(sessionId, JSON.stringify(response.access_token), { ex: accessToken.exp - Math.floor(Date.now() / 1000) });
84
+
85
+ cookies.delete(pkceCookieName, { path: "/" });
86
+ cookies.delete(stateCookieName, { path: "/" });
87
+
88
+ return new Response(null, { status: 302, headers });
89
+ }
@@ -0,0 +1,45 @@
1
+ import { CLIENT_ID, SERVER_URL, CACHE_SECRET } from '$env/static/private';
2
+ import { type RequestHandler } from '@sveltejs/kit';
3
+ import * as o from "oauth4webapi";
4
+ import { saveCookie } from '@functionalcms/svelte-components/index-server.js';
5
+
6
+ const pkceCookieName = "functionalcms_pkce";
7
+ const stateCookieName = "functionalcms_state";
8
+
9
+ function generateRandomState() {
10
+ const array = new Uint8Array(16);
11
+ crypto.getRandomValues(array);
12
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
13
+ }
14
+
15
+ async function createLoginHeader(serverUrl: string, clientId: string, callbackUrl: URL) {
16
+ const code_verifier = o.generateRandomCodeVerifier();
17
+ const code_challenge = await o.calculatePKCECodeChallenge(code_verifier);
18
+ const state = generateRandomState();
19
+
20
+ const authUrl = new URL(serverUrl + '/login/oauth/authorize');
21
+ authUrl.searchParams.set('client_id', clientId);
22
+ authUrl.searchParams.set('redirect_uri', callbackUrl.toString());
23
+ authUrl.searchParams.set('response_type', 'code');
24
+ authUrl.searchParams.set('state', state);
25
+ authUrl.searchParams.set('code_challenge', code_challenge);
26
+ authUrl.searchParams.set('code_challenge_method', 'S256');
27
+ authUrl.searchParams.set('scope', 'openid profile email user.extended properties');
28
+
29
+ return { state, codeVerifier: code_verifier, authorisationUrl: authUrl.toString() };
30
+ }
31
+
32
+ /** @type {import('./$types').RequestHandler} */
33
+ export const GET: RequestHandler = async ({ params, locals, url }) => {
34
+ const callbackUrl = new URL(`${url.origin}/auth/callback`);
35
+ const response = await createLoginHeader(SERVER_URL, CLIENT_ID, callbackUrl);
36
+
37
+ const headers = new Headers();
38
+ headers.set('Location', response.authorisationUrl);
39
+
40
+ await saveCookie(headers, CACHE_SECRET, pkceCookieName, response.codeVerifier);
41
+ await saveCookie(headers, CACHE_SECRET, stateCookieName, response.state);
42
+
43
+
44
+ return new Response(null, { status: 302, headers });
45
+ }