11import { env } from '$env/dynamic/private' ;
2- import { error } from '@sveltejs/kit ' ;
2+ import { getCurrentUser } from '$lib/server/api/v1/auth_api ' ;
33import { getLogger } from '$lib/server/logger.js' ;
4+ import { error , redirect } from '@sveltejs/kit' ;
45
56const logger = getLogger ( 'hooks' ) ;
67
8+ if ( env . FRACTAL_SERVER_HOST . endsWith ( '/' ) ) {
9+ env . FRACTAL_SERVER_HOST = env . FRACTAL_SERVER_HOST . substring (
10+ 0 ,
11+ env . FRACTAL_SERVER_HOST . length - 1
12+ ) ;
13+ logger . trace (
14+ 'Removing final slash from FRACTAL_SERVER_HOST, new value is %s' ,
15+ env . FRACTAL_SERVER_HOST
16+ ) ;
17+ }
18+
719export async function handle ( { event, resolve } ) {
20+ if ( event . url . pathname . startsWith ( '/api' ) ) {
21+ // API page - AJAX request - handled in proxy'
22+ logger . trace ( 'API endpoint detected, leaving the handling to the proxy' ) ;
23+ return await resolve ( event ) ;
24+ }
25+
26+ if ( event . route . id === null ) {
27+ if ( event . url . pathname . startsWith ( '/_app' ) ) {
28+ // The _app folder contains static files that are the result of npm build.
29+ // The hooks handle function is not called when loading valid static files, however we can
30+ // reach this point if a not existing file is requested. That could happen after an update
31+ // if the browser is loading a cached page that references to outdated contents.
32+ // In that case we can usually ignore the logs.
33+ logger . trace ( '[%s] - %s - [NOT FOUND]' , event . request . method , event . url . pathname ) ;
34+ } else {
35+ logger . info ( '[%s] - %s - [NOT FOUND]' , event . request . method , event . url . pathname ) ;
36+ }
37+ throw error ( 404 , 'Route not found' ) ;
38+ }
39+
840 logger . info ( '[%s] - %s' , event . request . method , event . url . pathname ) ;
941
10- if (
42+ // Retrieve server info (alive and version)
43+ const serverInfo = await getServerInfo ( event . fetch ) ;
44+
45+ // Check if auth cookie is present
46+ const fastApiUsersAuth = event . cookies . get ( 'fastapiusersauth' ) ;
47+ if ( ! fastApiUsersAuth ) {
48+ logger . debug ( 'No auth cookie found' ) ;
49+ }
50+
51+ // Retrieve user info
52+ let userInfo = null ;
53+ if ( serverInfo . alive && fastApiUsersAuth ) {
54+ userInfo = await getUserInfo ( event . fetch ) ;
55+ logger . trace ( 'User: %s' , userInfo ?. email ) ;
56+ }
57+
58+ // Store the pageInfo in locals variable to use the in +layout.server.js
59+ event . locals [ 'pageInfo' ] = { serverInfo, userInfo } ;
60+
61+ const isPublicPage =
1162 event . url . pathname == '/' ||
1263 event . url . pathname . startsWith ( '/auth' ) ||
13- event . url . pathname . startsWith ( '/sandbox/jsonschema' )
14- ) {
64+ event . url . pathname . startsWith ( '/sandbox/jsonschema' ) ;
65+
66+ if ( isPublicPage ) {
1567 logger . debug ( 'Public page - No auth required' ) ;
1668 return await resolve ( event ) ;
1769 }
1870
19- if ( event . url . pathname . startsWith ( '/api' ) ) {
20- // API page - AJAX request - handled in proxy'
21- return await resolve ( event ) ;
71+ if ( ! serverInfo . alive && ! isPublicPage ) {
72+ // If fractal-server is not available, redirect to the home page to display the maintenance banner
73+ throw redirect ( 302 , '/?invalidate=true' ) ;
2274 }
2375
2476 // Authentication guard
25- const fastApiUsersAuth = event . cookies . get ( 'fastapiusersauth' ) ;
26- if ( ! fastApiUsersAuth ) {
77+ if ( ! isPublicPage && userInfo === null ) {
2778 logger . debug ( 'Authentication required - No auth cookie found - Redirecting to login' ) ;
28- return new Response ( null , {
29- status : 302 ,
30- headers : { location : '/auth/login?invalidate=true' }
31- } ) ;
32- }
33-
34- const currentUser = await event . fetch ( `${ env . FRACTAL_SERVER_HOST } /auth/current-user/` ) ;
35- if ( ! currentUser . ok ) {
36- logger . debug ( 'Validation of authentication - Error loading user info' ) ;
37- return new Response ( null , {
38- status : 302 ,
39- headers : { location : '/auth/login?invalidate=true' }
40- } ) ;
79+ throw redirect ( 302 , '/auth/login?invalidate=true' ) ;
4180 }
4281
82+ // Admin area check
4383 if ( event . url . pathname . startsWith ( '/v1/admin' ) || event . url . pathname . startsWith ( '/v2/admin' ) ) {
44- const user = await currentUser . json ( ) ;
45- if ( ! user . is_superuser ) {
84+ if ( ! ( /** @type {import('$lib/types').User } */ ( userInfo ) . is_superuser ) ) {
4685 throw error ( 403 , `Only superusers can access the admin area` ) ;
4786 }
4887 }
@@ -57,11 +96,6 @@ export async function handleFetch({ event, request, fetch }) {
5796 1. https://github.com/fractal-analytics-platform/fractal-web/issues/274
5897 2. https://kit.svelte.dev/docs/hooks#server-hooks-handlefetch
5998 */
60-
61- if ( env . FRACTAL_SERVER_HOST . endsWith ( '/' ) ) {
62- env . FRACTAL_SERVER_HOST = env . FRACTAL_SERVER_HOST . substring ( 0 , env . FRACTAL_SERVER_HOST . length - 1 ) ;
63- }
64-
6599 if ( request . url . startsWith ( env . FRACTAL_SERVER_HOST ) ) {
66100 logger . trace ( 'Including cookie into request to %s, via handleFetch' , request . url ) ;
67101 const cookie = event . request . headers . get ( 'cookie' ) ;
@@ -71,3 +105,41 @@ export async function handleFetch({ event, request, fetch }) {
71105 }
72106 return fetch ( request ) ;
73107}
108+
109+ /**
110+ * @param {typeof fetch } fetch
111+ * @returns {Promise<{ alive: boolean, version: string | null }> }
112+ */
113+ async function getServerInfo ( fetch ) {
114+ let serverInfo = { alive : false , version : null } ;
115+
116+ try {
117+ const serverInfoResponse = await fetch ( env . FRACTAL_SERVER_HOST + '/api/alive/' ) ;
118+ if ( serverInfoResponse . ok ) {
119+ serverInfo = await serverInfoResponse . json ( ) ;
120+ logger . debug ( 'Server info loaded: Alive %s - %s' , serverInfo . alive , serverInfo . version ) ;
121+ } else {
122+ logger . error (
123+ 'Alive endpoint replied with unsuccessful status code %d' ,
124+ serverInfoResponse . status
125+ ) ;
126+ }
127+ } catch ( error ) {
128+ logger . fatal ( 'Error loading server info' , error ) ;
129+ }
130+
131+ return serverInfo ;
132+ }
133+
134+ /**
135+ * @param {typeof fetch } fetch
136+ * @returns {Promise<import('$lib/types').User|null> }
137+ */
138+ async function getUserInfo ( fetch ) {
139+ try {
140+ return await getCurrentUser ( fetch ) ;
141+ } catch ( error ) {
142+ logger . error ( 'Error loading user info' , error ) ;
143+ return null ;
144+ }
145+ }
0 commit comments