@functionalcms/svelte-components 5.1.7 → 5.2.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.
Files changed (31) hide show
  1. package/dist/components/form/form.d.ts +1 -1
  2. package/dist/index-server.d.ts +3 -3
  3. package/dist/index-server.js +3 -3
  4. package/dist/server-side/auth/authentication.d.ts +17 -0
  5. package/dist/server-side/auth/authentication.js +72 -0
  6. package/dist/server-side/auth/authorizationHandle.js +2 -2
  7. package/dist/server-side/auth/machineAuthenticationProvider.d.ts +2 -7
  8. package/dist/server-side/auth/machineAuthenticationProvider.js +28 -64
  9. package/dist/server-side/auth/userAuthenticationProvider.d.ts +2 -7
  10. package/dist/server-side/auth/userAuthenticationProvider.js +41 -93
  11. package/dist/server-side/cookies.d.ts +3 -0
  12. package/dist/server-side/cookies.js +22 -0
  13. package/dist/server-side/crypto.d.ts +2 -0
  14. package/dist/server-side/crypto.js +20 -0
  15. package/dist/server-side/functionalSequence.d.ts +2 -0
  16. package/dist/server-side/functionalSequence.js +8 -0
  17. package/dist/server-side/handlers/sessionHandler.d.ts +3 -0
  18. package/dist/server-side/handlers/sessionHandler.js +15 -0
  19. package/package.json +1 -1
  20. package/dist/server-side/auth/authenticationHandle.d.ts +0 -3
  21. package/dist/server-side/auth/authenticationHandle.js +0 -67
  22. package/dist/server-side/auth/getMachineAccessToken.d.ts +0 -3
  23. package/dist/server-side/auth/getMachineAccessToken.js +0 -25
  24. package/dist/server-side/auth/redisSessionProvider.d.ts +0 -14
  25. package/dist/server-side/auth/redisSessionProvider.js +0 -38
  26. package/dist/server-side/auth/sessionIdGenerator.d.ts +0 -2
  27. package/dist/server-side/auth/sessionIdGenerator.js +0 -3
  28. package/dist/server-side/auth/tokenRefresh.d.ts +0 -3
  29. package/dist/server-side/auth/tokenRefresh.js +0 -47
  30. package/dist/server-side/auth/types.d.ts +0 -45
  31. package/dist/server-side/auth/types.js +0 -6
@@ -73,7 +73,7 @@ export type PhoneInputField = FieldDef & {
73
73
  size: "md" | "sm" | "lg";
74
74
  placeholder?: string;
75
75
  };
76
- export type Field = InputField | TextareaField | CollectionField | SelectField | FilePickerField | TimePickerField | PhoneInputField;
76
+ export type Field = InputField | TextareaField | CollectionField | SelectField | FilePickerField | TimePickerField | PhoneInputField | HiddenField;
77
77
  export declare enum SubmitResult {
78
78
  Success = "success",
79
79
  Error = "error"
@@ -1,12 +1,12 @@
1
- export { authenticationHandle } from './server-side/auth/authenticationHandle.js';
2
1
  export { default as authorizationHandle } from './server-side/auth/authorizationHandle.js';
3
2
  export { default as errorHandler } from './server-side/auth/errorHandle.js';
4
- export { redisSessionProvider } from './server-side/auth/redisSessionProvider.js';
3
+ export { authenticationHandle } from './server-side/auth/authentication.js';
5
4
  export { machineAuthenticationProvider } from './server-side/auth/machineAuthenticationProvider.js';
6
5
  export { userAuthenticationProvider } from './server-side/auth/userAuthenticationProvider.js';
6
+ export { hookSequence } from './server-side/functionalSequence.js';
7
+ export { sessionHandler } from './server-side/handlers/sessionHandler.js';
7
8
  export { getCommunicationService, getDataService, getTemplateService, getWebsiteService, getAuthService, getFileService, } from './server-side/getServices.js';
8
9
  export type { RedirectResponse } from './server-side/auth/RedirectResponse.js';
9
- export { createMachineTokenApprovedLocals } from './server-side/auth/getMachineAccessToken.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';
@@ -1,11 +1,11 @@
1
- export { authenticationHandle } from './server-side/auth/authenticationHandle.js';
2
1
  export { default as authorizationHandle } from './server-side/auth/authorizationHandle.js';
3
2
  export { default as errorHandler } from './server-side/auth/errorHandle.js';
4
- export { redisSessionProvider } from './server-side/auth/redisSessionProvider.js';
3
+ export { authenticationHandle } from './server-side/auth/authentication.js';
5
4
  export { machineAuthenticationProvider } from './server-side/auth/machineAuthenticationProvider.js';
6
5
  export { userAuthenticationProvider } from './server-side/auth/userAuthenticationProvider.js';
6
+ export { hookSequence } from './server-side/functionalSequence.js';
7
+ export { sessionHandler } from './server-side/handlers/sessionHandler.js';
7
8
  export { getCommunicationService, getDataService, getTemplateService, getWebsiteService, getAuthService, getFileService, } from './server-side/getServices.js';
8
- export { createMachineTokenApprovedLocals } from './server-side/auth/getMachineAccessToken.js';
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';
@@ -0,0 +1,17 @@
1
+ import type { Handle } from "@sveltejs/kit";
2
+ export type CallbackResponse = {
3
+ access_token: string;
4
+ id_token?: string;
5
+ refresh_token?: string;
6
+ redirectionUrl: string;
7
+ };
8
+ export type SigninResponse = {
9
+ state: string;
10
+ codeVerifier: string;
11
+ authorisationUrl: string;
12
+ };
13
+ export type AuthenticationProvider = {
14
+ handleLogin(callbackUrl: URL): Promise<SigninResponse>;
15
+ handleCallback: (state: string, codeVerifier: string, eventUrl: URL) => Promise<CallbackResponse>;
16
+ };
17
+ export declare const authenticationHandle: (secret: string, provider: AuthenticationProvider) => Handle;
@@ -0,0 +1,72 @@
1
+ import { deleteCookie, loadCookie, saveCookie } from "../cookies";
2
+ const pkceCookieName = "functionalcms_pkce";
3
+ const stateCookieName = "functionalcms_state";
4
+ const accessTokenCookie = "functionalcms_access_token";
5
+ const idTokenCookie = "functionalcms_id_token";
6
+ const refreshTokenCookie = "functionalcms_refresh_token";
7
+ export const authenticationHandle = (secret, provider) => {
8
+ return async ({ event, resolve }) => {
9
+ try {
10
+ if (event.url.pathname === '/auth/callback') {
11
+ const headers = await handleCallback(event, secret, provider);
12
+ return new Response(null, { status: 302, headers });
13
+ }
14
+ else if (event.url.pathname === '/auth/login') {
15
+ const headers = await handleLogin(event, provider, secret);
16
+ return new Response(null, { status: 302, headers });
17
+ }
18
+ else if (event.url.pathname === '/auth/logout') {
19
+ handleLogout(event);
20
+ // } else {
21
+ // const hasUser = event.locals.user;
22
+ // const accessToken = await loadCookie(event.cookies, secret, accessTokenCookie);
23
+ // const idToken = await loadCookie(event.cookies, secret, idTokenCookie);
24
+ // console.log(`accessToken: ${accessToken}, idToken: ${idToken}, hasUser: ${hasUser}`);
25
+ // if (!hasUser && accessToken != "") {
26
+ // await loginUserWithToken(event, accessToken, idToken, provider);
27
+ // }
28
+ }
29
+ }
30
+ catch (error) {
31
+ console.error("Authentication error:", error);
32
+ }
33
+ return resolve(event);
34
+ };
35
+ };
36
+ function handleLogout(event) {
37
+ event.cookies.delete(accessTokenCookie, { path: "/" });
38
+ event.cookies.delete(idTokenCookie, { path: "/" });
39
+ event.cookies.delete(refreshTokenCookie, { path: "/" });
40
+ event.locals.user = null;
41
+ event.locals.session = null;
42
+ }
43
+ async function handleLogin(event, provider, secret) {
44
+ const callbackUrl = new URL(`${event.url.origin}/auth/callback`);
45
+ const response = await provider.handleLogin(callbackUrl);
46
+ const headers = new Headers();
47
+ headers.set('Location', response.authorisationUrl);
48
+ await saveCookie(headers, secret, pkceCookieName, response.codeVerifier);
49
+ await saveCookie(headers, secret, stateCookieName, response.state);
50
+ return headers;
51
+ }
52
+ async function handleCallback(event, secret, provider) {
53
+ const expectedState = await loadCookie(event.cookies, secret, stateCookieName);
54
+ const codeVerifier = await loadCookie(event.cookies, secret, pkceCookieName);
55
+ const response = await provider.handleCallback(expectedState, codeVerifier, event.url);
56
+ const headers = new Headers();
57
+ headers.set('Location', event.url.origin);
58
+ await saveCookie(headers, secret, accessTokenCookie, response.access_token);
59
+ if (response.id_token) {
60
+ await saveCookie(headers, secret, idTokenCookie, response.id_token);
61
+ }
62
+ if (response.refresh_token) {
63
+ await saveCookie(headers, secret, refreshTokenCookie, response.refresh_token);
64
+ }
65
+ await deleteCookie(event.cookies, headers, pkceCookieName);
66
+ await deleteCookie(event.cookies, headers, stateCookieName);
67
+ return headers;
68
+ }
69
+ // async function loginUserWithToken(event: any, accessToken: string, idToken: string, provider: AuthenticationProvider) {
70
+ // const user = await provider.retrieveUser(accessToken);
71
+ // event.locals.user = user;
72
+ // }
@@ -4,8 +4,8 @@ const authorizationHandle = () => {
4
4
  if (path.startsWith('/auth/') === false) {
5
5
  const route = event.route;
6
6
  if (route.id.includes('/(protect)')) {
7
- const session = event?.locals?.session;
8
- if (!session) {
7
+ const user = event?.locals?.user;
8
+ if (!user) {
9
9
  const cookieHeader = `retrunToAddress=${event.url.toString()}; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/`;
10
10
  return new Response('Login user', {
11
11
  status: 303,
@@ -1,7 +1,2 @@
1
- import type { Token } from './types.js';
2
- export declare const machineAuthenticationProvider: (AUTH_KEYCLOAK_ID: string, AUTH_KEYCLOAK_SECRET: string, AUTH_KEYCLOAK_ISSUER: string, scope?: string, redirectPath?: string) => {
3
- getAuthIdentity: (domain: string) => Promise<any>;
4
- getValidation: (event: any) => Promise<Token>;
5
- getUser: (token: Token) => Promise<any>;
6
- redirectPath: string;
7
- };
1
+ import type { AuthenticationProvider } from "./authentication";
2
+ export declare const machineAuthenticationProvider: (serverUrl: string, clientId: string, clientSecret: string, redirectUrl: string) => AuthenticationProvider;
@@ -1,69 +1,33 @@
1
- const getKeycloakIdentity = async (issuer, client_id, scope, redirectUrl) => {
2
- const headers = new Headers();
3
- headers.append('Location', '/auth/validate');
4
- return headers;
5
- };
6
- const getKeycloakValidation = async (issuer, client_id, client_secret, scope, event) => {
7
- const response = await fetch(`${issuer}/protocol/openid-connect/token`, {
8
- method: "POST",
9
- body: new URLSearchParams({
10
- grant_type: "client_credentials",
11
- client_id: client_id,
12
- client_secret: client_secret,
13
- scope,
14
- }),
15
- headers: {
16
- "Content-Type": "application/x-www-form-urlencoded",
17
- Accept: "application/json"
18
- }
19
- });
20
- if (!response.ok) {
21
- console.log('Response was NOT okay');
22
- throw new Error('Token not validated.');
23
- }
24
- const token = await response.json();
25
- return token;
26
- };
27
- const getUser = async (issuer, token) => {
28
- try {
29
- const accessToken = token.access_token;
30
- const response = await fetch(`${issuer}/protocol/openid-connect/userinfo`, {
1
+ export const machineAuthenticationProvider = (serverUrl, clientId, clientSecret, redirectUrl) => {
2
+ const handleLogin = (callbackUrl) => {
3
+ const response = {
4
+ state: "",
5
+ codeVerifier: "",
6
+ authorisationUrl: callbackUrl.toString(),
7
+ };
8
+ return Promise.resolve(response);
9
+ };
10
+ const handleCallback = async (state, codeVerifier, eventUrl) => {
11
+ const tokenRes = await fetch(`${serverUrl}/api/login/oauth/access_token`, {
31
12
  method: "POST",
32
- headers: {
33
- "Content-Type": "application/x-www-form-urlencoded",
34
- Accept: "application/json",
35
- Authorization: `Bearer ${accessToken}`
36
- }
13
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
14
+ body: new URLSearchParams({
15
+ grant_type: "client_credentials",
16
+ client_id: clientId,
17
+ client_secret: clientSecret,
18
+ scope: "profile openid email offline_access"
19
+ })
37
20
  });
38
- if (!response.ok) {
39
- throw new Error('Failed to fetch user information.');
40
- }
41
- const data = await response.json();
42
- return data;
43
- }
44
- catch (error) {
45
- return new Response(null, {
46
- status: 400
47
- });
48
- }
49
- };
50
- export const machineAuthenticationProvider = (AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, AUTH_KEYCLOAK_ISSUER, scope = '', redirectPath = '/') => {
51
- const extendedScope = `openid profile offline_access ${scope}`;
52
- const redirectUrl = "/auth/validate";
21
+ const tokens = await tokenRes.json();
22
+ return {
23
+ access_token: tokens.access_token,
24
+ id_token: tokens.id_token,
25
+ refresh_token: tokens.refresh_token,
26
+ redirectionUrl: redirectUrl,
27
+ };
28
+ };
53
29
  return {
54
- getAuthIdentity: async (domain) => {
55
- const provider = await getKeycloakIdentity(AUTH_KEYCLOAK_ISSUER, AUTH_KEYCLOAK_ID, extendedScope, `${domain}${redirectUrl}`);
56
- return provider;
57
- },
58
- getValidation: async (event) => {
59
- const fullRedirectUrl = `${event.url.origin}${redirectUrl}`;
60
- const token = await getKeycloakValidation(AUTH_KEYCLOAK_ISSUER, AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, extendedScope, event);
61
- return token;
62
- },
63
- getUser: async (token) => {
64
- const user = await getUser(AUTH_KEYCLOAK_ISSUER, token);
65
- return user;
66
- },
67
- redirectPath
30
+ handleLogin,
31
+ handleCallback,
68
32
  };
69
33
  };
@@ -1,7 +1,2 @@
1
- import type { Token } from './types.js';
2
- export declare const userAuthenticationProvider: (AUTH_KEYCLOAK_ID: string, AUTH_KEYCLOAK_ISSUER: string, scope?: string, redirectPath?: string) => {
3
- getAuthIdentity: (domain: string) => Promise<any>;
4
- getValidation: (event: any) => Promise<Token>;
5
- getUser: (token: Token) => Promise<any>;
6
- redirectPath: string;
7
- };
1
+ import type { AuthenticationProvider } from "./authentication";
2
+ export declare const userAuthenticationProvider: (serverUrl: string, clientId: string) => AuthenticationProvider;
@@ -1,97 +1,45 @@
1
1
  import * as o from "oauth4webapi";
2
- const authStateCookieName = 'auth_state';
3
- const stateIdGenerator = () => crypto.randomUUID();
4
- const getKeycloakIdentity = async (issuer, client_id, scope, redirectUrl) => {
5
- const state = stateIdGenerator();
6
- const cookieHeader = `${authStateCookieName}=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/`;
7
- const code_challenge = await o.calculatePKCECodeChallenge(state);
8
- const authorizationUrlSearchParams = new URLSearchParams({
9
- client_id: client_id,
10
- redirect_uri: redirectUrl,
11
- response_type: 'code',
12
- state,
13
- scope,
14
- code_challenge
15
- });
16
- const authorizationUrl = `${issuer}/protocol/openid-connect/auth?${authorizationUrlSearchParams}`;
17
- const headers = new Headers();
18
- headers.append('Set-Cookie', cookieHeader);
19
- headers.append('Location', authorizationUrl);
20
- return headers;
21
- };
22
- const getKeycloakValidation = async (issuer, client_id, scope, event, redirectUrl) => {
23
- const storedState = event.cookies.get(authStateCookieName);
24
- const state = event.url.searchParams.get("state");
25
- if (!storedState || !state || storedState !== state) {
26
- throw new Error('State not valid');
27
- }
28
- const code = event.url.searchParams.get("code");
29
- if (!code) {
30
- throw new Error('Challenge code not valid');
31
- }
32
- const code_challenge = await o.calculatePKCECodeChallenge(state);
33
- const response = await fetch(`${issuer}/protocol/openid-connect/token`, {
34
- method: "POST",
35
- body: new URLSearchParams({
36
- grant_type: "authorization_code",
37
- client_id: client_id,
38
- redirect_uri: redirectUrl,
39
- code_verifier: code_challenge,
40
- code,
41
- scope,
42
- }),
43
- headers: {
44
- "Content-Type": "application/x-www-form-urlencoded",
45
- Accept: "application/json"
46
- }
47
- });
48
- if (!response.ok) {
49
- console.log('Response was NOT okay');
50
- throw new Error('Token not validated.');
51
- }
52
- const token = await response.json();
53
- return token;
54
- };
55
- const getUser = async (issuer, token) => {
56
- try {
57
- const accessToken = token.access_token;
58
- const response = await fetch(`${issuer}/protocol/openid-connect/userinfo`, {
59
- method: "POST",
60
- headers: {
61
- "Content-Type": "application/x-www-form-urlencoded",
62
- Accept: "application/json",
63
- Authorization: `Bearer ${accessToken}`
64
- }
65
- });
66
- if (!response.ok) {
67
- throw new Error('Failed to fetch user information.');
68
- }
69
- const data = await response.json();
70
- return data;
71
- }
72
- catch (error) {
73
- return new Response(null, {
74
- status: 400
75
- });
76
- }
77
- };
78
- export const userAuthenticationProvider = (AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_ISSUER, scope = '', redirectPath = '/') => {
79
- const extendedScope = `openid profile offline_access ${scope}`;
80
- const redirectUrl = "/auth/validate";
2
+ function generateRandomState() {
3
+ const array = new Uint8Array(16);
4
+ crypto.getRandomValues(array);
5
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
6
+ }
7
+ export const userAuthenticationProvider = (serverUrl, clientId) => {
8
+ const client = {
9
+ client_id: clientId,
10
+ token_endpoint_auth_method: "none"
11
+ };
12
+ const issuer = new URL(serverUrl);
13
+ const handleLogin = async (callbackUrl) => {
14
+ const code_verifier = o.generateRandomCodeVerifier();
15
+ const code_challenge = await o.calculatePKCECodeChallenge(code_verifier);
16
+ const state = generateRandomState();
17
+ const authUrl = new URL(serverUrl + '/login/oauth/authorize');
18
+ authUrl.searchParams.set('client_id', clientId);
19
+ authUrl.searchParams.set('redirect_uri', callbackUrl.toString());
20
+ authUrl.searchParams.set('response_type', 'code');
21
+ authUrl.searchParams.set('state', state);
22
+ authUrl.searchParams.set('code_challenge', code_challenge);
23
+ authUrl.searchParams.set('code_challenge_method', 'S256');
24
+ return { state, codeVerifier: code_verifier, authorisationUrl: authUrl.toString() };
25
+ };
26
+ const handleCallback = async (state, codeVerifier, callbackUrl) => {
27
+ const discovery = await o.discoveryRequest(issuer);
28
+ const metadata = await o.processDiscoveryResponse(issuer, discovery);
29
+ const callbackParameters = o.validateAuthResponse(metadata, client, callbackUrl, state);
30
+ // Exchange code for tokens
31
+ const tokenResponse = await o.authorizationCodeGrantRequest(metadata, client, o.None(), callbackParameters, callbackUrl.toString(), codeVerifier);
32
+ // Parse tokens
33
+ const tokens = await o.processAuthorizationCodeResponse(metadata, client, tokenResponse);
34
+ return {
35
+ access_token: tokens.access_token,
36
+ id_token: tokens.id_token,
37
+ refresh_token: tokens.refresh_token,
38
+ redirectionUrl: callbackUrl.toString(),
39
+ };
40
+ };
81
41
  return {
82
- getAuthIdentity: async (domain) => {
83
- const provider = await getKeycloakIdentity(AUTH_KEYCLOAK_ISSUER, AUTH_KEYCLOAK_ID, extendedScope, `${domain}${redirectUrl}`);
84
- return provider;
85
- },
86
- getValidation: async (event) => {
87
- const fullRedirectUrl = `${event.url.origin}${redirectUrl}`;
88
- const token = await getKeycloakValidation(AUTH_KEYCLOAK_ISSUER, AUTH_KEYCLOAK_ID, extendedScope, event, fullRedirectUrl);
89
- return token;
90
- },
91
- getUser: async (token) => {
92
- const user = await getUser(AUTH_KEYCLOAK_ISSUER, token);
93
- return user;
94
- },
95
- redirectPath
42
+ handleLogin,
43
+ handleCallback,
96
44
  };
97
45
  };
@@ -0,0 +1,3 @@
1
+ export declare const saveCookie: (headers: Headers, secret: string, name: string, value: string) => Promise<void>;
2
+ export declare const loadCookie: (cookies: any, secret: string, name: string) => Promise<string>;
3
+ export declare const deleteCookie: (cookies: any, headers: Headers, cookieName: string) => void;
@@ -0,0 +1,22 @@
1
+ import { decrypt, encrypt } from "./crypto";
2
+ export const saveCookie = async (headers, secret, name, value) => {
3
+ const encryptedValue = await encrypt(secret, value);
4
+ headers.append('Set-Cookie', `${name}=${encryptedValue}; HttpOnly; SameSite=None; Secure; Max-Age=3600; Path=/`);
5
+ };
6
+ export const loadCookie = async (cookies, secret, name) => {
7
+ const encryptedValue = cookies.get(name);
8
+ if (encryptedValue) {
9
+ return await decrypt(secret, encryptedValue);
10
+ }
11
+ else {
12
+ return "";
13
+ }
14
+ };
15
+ export const deleteCookie = (cookies, headers, cookieName) => {
16
+ cookies.delete(cookieName, { path: "/" });
17
+ cookies.set(cookieName, '', {
18
+ path: '/',
19
+ maxAge: 0
20
+ });
21
+ headers.append('Set-Cookie', `${cookieName}=empty; HttpOnly; SameSite=None; Secure; Max-Age=0; Path=/`);
22
+ };
@@ -0,0 +1,2 @@
1
+ export declare function encrypt(secret: string, value: string): Promise<string>;
2
+ export declare function decrypt(secret: string, value: string): Promise<string>;
@@ -0,0 +1,20 @@
1
+ // src/lib/crypto.ts
2
+ const keyPromise = (secret) => crypto.subtle.importKey("raw", Uint8Array.from(atob(secret), c => c.charCodeAt(0)), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
3
+ export async function encrypt(secret, value) {
4
+ const key = await keyPromise(secret);
5
+ const iv = crypto.getRandomValues(new Uint8Array(12));
6
+ const encoded = new TextEncoder().encode(value);
7
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
8
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
9
+ combined.set(iv);
10
+ combined.set(new Uint8Array(ciphertext), iv.length);
11
+ return btoa(String.fromCharCode(...combined));
12
+ }
13
+ export async function decrypt(secret, value) {
14
+ const key = await keyPromise(secret);
15
+ const data = Uint8Array.from(atob(value), c => c.charCodeAt(0));
16
+ const iv = data.slice(0, 12);
17
+ const ciphertext = data.slice(12);
18
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
19
+ return new TextDecoder().decode(plaintext);
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { Handle } from "@sveltejs/kit";
2
+ export declare const hookSequence: (secret: string, paraglideMiddleware: any, getTextDirection: any, customHooks: Handle[]) => Handle;
@@ -0,0 +1,8 @@
1
+ import { sequence } from "@sveltejs/kit/hooks";
2
+ import errorHandler from "./auth/errorHandle";
3
+ import { paraglideHandler } from "./handlers/paraglideHandler";
4
+ import sessionHandler from "./handlers/sessionHandler";
5
+ import authorizationHandle from "./auth/authorizationHandle";
6
+ export const hookSequence = (secret, paraglideMiddleware, getTextDirection, customHooks) => {
7
+ return sequence(sessionHandler(secret), errorHandler, paraglideHandler(paraglideMiddleware, getTextDirection), authorizationHandle(), ...customHooks);
8
+ };
@@ -0,0 +1,3 @@
1
+ import { type Handle } from '@sveltejs/kit';
2
+ export declare const sessionHandler: (secret: string) => Handle;
3
+ export default sessionHandler;
@@ -0,0 +1,15 @@
1
+ import {} from '@sveltejs/kit';
2
+ import { loadCookie, saveCookie } from '../cookies';
3
+ export const sessionHandler = (secret) => {
4
+ return async ({ event, resolve }) => {
5
+ const session = event?.locals?.session;
6
+ if (!session) {
7
+ let session = await loadCookie(event.cookies, secret, "session");
8
+ event.locals.session = session;
9
+ }
10
+ const result = await resolve(event);
11
+ await saveCookie(result.headers, secret, "session", event.locals.session);
12
+ return result;
13
+ };
14
+ };
15
+ export default sessionHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@functionalcms/svelte-components",
3
- "version": "5.1.7",
3
+ "version": "5.2.0",
4
4
  "license": "MIT",
5
5
  "watch": {
6
6
  "build": {
@@ -1,3 +0,0 @@
1
- import { type Handle } from '@sveltejs/kit';
2
- import type { IProvider, ISessionStorage } from './types.js';
3
- export declare const authenticationHandle: (provider: IProvider, sessionProvider: ISessionStorage) => Handle;
@@ -1,67 +0,0 @@
1
- import {} from '@sveltejs/kit';
2
- const authSessionCookieName = `auth_session`;
3
- const logout = async (cookies, sessionProvider, afterLogoutPath = '/') => {
4
- const headers = new Headers();
5
- const state = cookies.get(authSessionCookieName);
6
- if (state) {
7
- cookies.delete('auth_session', { path: '/' });
8
- const sid = cookies.get('auth_session');
9
- await sessionProvider.deleteSession(sid);
10
- headers.append('Set-Cookie', `${authSessionCookieName}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`);
11
- headers.append('Location', afterLogoutPath);
12
- }
13
- else {
14
- headers.append('Location', '/');
15
- }
16
- return headers;
17
- };
18
- const createUserSession = async (provider, event, sessionProvider) => {
19
- const token = await provider.getValidation(event);
20
- const session = await provider.getUser(token);
21
- const headers = new Headers();
22
- if (session !== undefined) {
23
- session.userId = session.sub;
24
- event.locals.session = session;
25
- event.locals.accessToken = token.access_token;
26
- const sessionId = await sessionProvider.createSession({ session, token }, token.expires_in);
27
- const retrunToAddress = event.cookies.get("retrunToAddress");
28
- headers.append('Location', retrunToAddress ? retrunToAddress : provider.redirectPath);
29
- headers.append('Set-Cookie', `${authSessionCookieName}=${sessionId}; HttpOnly; Secure; SameSite=Lax; Max-Age=${token.expires_in + 120}; Path=/`);
30
- headers.append('Set-Cookie', `retrunToAddress=; HttpOnly; Secure; SameSite=Lax; Max-Age=${token.expires_in + 120}; Path=/`);
31
- }
32
- else {
33
- headers.append('Location', '/');
34
- }
35
- return headers;
36
- };
37
- const loadUserFromSession = async (cookies, locals, sessionProvider) => {
38
- const sid = cookies.get(authSessionCookieName);
39
- const session = await sessionProvider.getSession(sid);
40
- if (session) {
41
- locals.session = session.session;
42
- locals.token = session.token;
43
- }
44
- else {
45
- locals.username = "";
46
- }
47
- };
48
- export const authenticationHandle = (provider, sessionProvider) => {
49
- return async ({ event, resolve }) => {
50
- if (event.url.pathname.startsWith('/')) {
51
- await loadUserFromSession(event.cookies, event.locals, sessionProvider);
52
- }
53
- if (event.url.pathname === '/auth/logout') {
54
- const headers = await logout(event.cookies, sessionProvider);
55
- return new Response('Logging Out...', { status: 303, headers });
56
- }
57
- else if (event.url.pathname === '/auth/login') {
58
- const authProvider = await provider.getAuthIdentity(event.url.origin);
59
- return new Response('Redirect', { status: 302, headers: authProvider });
60
- }
61
- else if (event.url.pathname === "/auth/validate") {
62
- const validationResponse = await createUserSession(provider, event, sessionProvider);
63
- return new Response('Redirect', { status: 302, headers: validationResponse });
64
- }
65
- return await resolve(event);
66
- };
67
- };
@@ -1,3 +0,0 @@
1
- export declare const createMachineTokenApprovedLocals: (AUTH_KEYCLOAK_ID: string, AUTH_KEYCLOAK_SECRET: string, AUTH_KEYCLOAK_ISSUER: string, scope?: string) => Promise<{
2
- token: any;
3
- }>;
@@ -1,25 +0,0 @@
1
- export const createMachineTokenApprovedLocals = async (AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, AUTH_KEYCLOAK_ISSUER, scope = '') => {
2
- const url = `${AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/token`;
3
- const body = new URLSearchParams({
4
- grant_type: "client_credentials",
5
- client_id: AUTH_KEYCLOAK_ID,
6
- client_secret: AUTH_KEYCLOAK_SECRET,
7
- scope: `openid profile offline_access ${scope}`,
8
- });
9
- const response = await fetch(url, {
10
- method: "POST",
11
- body: body,
12
- headers: {
13
- "Content-Type": "application/x-www-form-urlencoded",
14
- Accept: "*/*"
15
- }
16
- });
17
- if (!response.ok) {
18
- console.log('Response was NOT okay');
19
- throw new Error('Token not validated.');
20
- }
21
- const token = await response.json();
22
- return {
23
- token: token,
24
- };
25
- };
@@ -1,14 +0,0 @@
1
- import type { ISession, Sid } from './types.js';
2
- declare function createSession(session: ISession, maxAge: number): Promise<string>;
3
- declare function updateSession(sid: string, session: ISession, maxAge: number): Promise<void>;
4
- declare function getSession(sid: Sid): Promise<any>;
5
- declare function deleteSession(sid: string): Promise<void>;
6
- declare function clean(): Promise<void>;
7
- export declare const redisSessionProvider: {
8
- clean: typeof clean;
9
- createSession: typeof createSession;
10
- getSession: typeof getSession;
11
- deleteSession: typeof deleteSession;
12
- updateSession: typeof updateSession;
13
- };
14
- export {};
@@ -1,38 +0,0 @@
1
- import { getSid } from './sessionIdGenerator.js';
2
- const sessionStore = new Map();
3
- async function createSession(session, maxAge) {
4
- const sid = getSid();
5
- const sessionObject = {
6
- data: session,
7
- invalidAt: Date.now() + maxAge + 3600
8
- };
9
- sessionStore.set(sid, sessionObject);
10
- return sid;
11
- }
12
- async function updateSession(sid, session, maxAge) {
13
- sessionStore.delete(sid);
14
- const sessionObject = {
15
- data: session,
16
- invalidAt: Date.now() + maxAge + 3600
17
- };
18
- sessionStore.set(sid, sessionObject);
19
- }
20
- async function getSession(sid) {
21
- const savedSession = sessionStore.get(sid);
22
- if (savedSession) {
23
- return savedSession.data;
24
- }
25
- return null;
26
- }
27
- async function deleteSession(sid) {
28
- sessionStore.delete(sid);
29
- }
30
- async function clean() {
31
- }
32
- export const redisSessionProvider = {
33
- clean: clean,
34
- createSession: createSession,
35
- getSession: getSession,
36
- deleteSession: deleteSession,
37
- updateSession: updateSession
38
- };
@@ -1,2 +0,0 @@
1
- import type { Sid } from "./types.js";
2
- export declare function getSid(): Sid;
@@ -1,3 +0,0 @@
1
- export function getSid() {
2
- return crypto.randomUUID();
3
- }
@@ -1,3 +0,0 @@
1
- import { type Handle } from '@sveltejs/kit';
2
- import type { ISessionStorage } from './types.js';
3
- export declare const tokenRefreshHandle: (sessionProvider: ISessionStorage, AUTH_KEYCLOAK_ID: string, AUTH_KEYCLOAK_SECRET: string, AUTH_KEYCLOAK_ISSUER: string) => Handle;
@@ -1,47 +0,0 @@
1
- import {} from '@sveltejs/kit';
2
- const authSessionCookieName = `auth_session`;
3
- function isTokenExpired(token) {
4
- const base64Url = token.split(".")[1];
5
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
6
- const jsonPayload = decodeURIComponent(atob(base64)
7
- .split("")
8
- .map(function (c) {
9
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
10
- })
11
- .join(""));
12
- const { exp } = JSON.parse(jsonPayload);
13
- const expired = Date.now() >= exp * 1000;
14
- return expired;
15
- }
16
- async function refreshToken(clientId, clientSecret, issuer, refresh_token) {
17
- const response = await fetch(`${issuer}/protocol/openid-connect/token`, {
18
- method: "POST",
19
- body: new URLSearchParams({
20
- grant_type: "refresh_token",
21
- client_id: clientId,
22
- client_secret: clientSecret,
23
- refresh_token: refresh_token,
24
- }),
25
- headers: {
26
- "Content-Type": "application/x-www-form-urlencoded",
27
- Accept: "application/json"
28
- }
29
- });
30
- const newToken = await response.json();
31
- return newToken;
32
- }
33
- export const tokenRefreshHandle = (sessionProvider, AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, AUTH_KEYCLOAK_ISSUER) => {
34
- return async ({ event, resolve }) => {
35
- const locals = event.locals;
36
- if (locals?.token?.refresh_token) {
37
- const isExpired = isTokenExpired(locals.token.access_token);
38
- if (isExpired) {
39
- const newToken = await refreshToken(AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, AUTH_KEYCLOAK_ISSUER, locals.token.refresh_token);
40
- locals.token = newToken;
41
- const sid = event.cookies.get(authSessionCookieName);
42
- sessionProvider.updateSession(sid, locals, newToken.expires_in);
43
- }
44
- }
45
- return await resolve(event);
46
- };
47
- };
@@ -1,45 +0,0 @@
1
- export declare class Token {
2
- access_token: string;
3
- expires_in: number;
4
- id_token?: string;
5
- refresh_expires_in?: number;
6
- refresh_token?: string;
7
- scope?: string;
8
- session_state?: string;
9
- token_type?: string;
10
- }
11
- export interface Session {
12
- client_id: string;
13
- email_verified: string;
14
- preferred_username: string;
15
- sub: string;
16
- userId: string;
17
- }
18
- export interface Locals {
19
- session: Session | undefined;
20
- auth_token: string;
21
- }
22
- export type ISession = {
23
- session: Session;
24
- token: Token;
25
- };
26
- export type SessionInfoCache = {
27
- data: ISession;
28
- invalidAt: number;
29
- };
30
- export type Sid = string;
31
- export interface IProvider {
32
- redirectPath: string;
33
- getAuthIdentity(domain: string): Promise<any>;
34
- getValidation(event: any): Promise<Token>;
35
- getUser(token: Token): any;
36
- }
37
- export interface ISessionStorage {
38
- clean(): Promise<void>;
39
- createSession(session: ISession, maxAge: number): Promise<Sid>;
40
- getSession(sid: Sid): Promise<ISession | undefined>;
41
- deleteSession(sid: Sid): Promise<void>;
42
- updateSession(sid: Sid, session: ISession, maxAge: number): Promise<void>;
43
- }
44
- export interface Cookies {
45
- }
@@ -1,6 +0,0 @@
1
- export class Token {
2
- constructor() {
3
- this.access_token = "";
4
- this.expires_in = 0;
5
- }
6
- }