From 33f087d38f2c8e5022d6ae7bfaecb65861f65afb Mon Sep 17 00:00:00 2001 From: Danny Avila <110412045+danny-avila@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:10:46 -0400 Subject: [PATCH] feat: Refresh Token for improved Session Security (#927) * feat(api): refresh token logic * feat(client): refresh token logic * feat(data-provider): refresh token logic * fix: SSE uses esm * chore: add default refresh token expiry to AuthService, add message about env var not set when generating a token * chore: update scripts to more compatible bun methods, ran bun install again * chore: update env.example and playwright workflow with JWT_REFRESH_SECRET * chore: update breaking changes docs * chore: add timeout to url visit * chore: add default SESSION_EXPIRY in generateToken logic, add act script for testing github actions * fix(e2e): refresh automatically in development environment to pass e2e tests --- .env.example | 10 +- .github/workflows/playwright.yml | 1 + api/models/Session.js | 59 +++++++++ api/models/User.js | 26 +--- api/server/controllers/AuthController.js | 114 +++++++++--------- .../controllers/auth/LoginController.js | 11 +- .../controllers/auth/LogoutController.js | 7 +- api/server/routes/auth.js | 4 +- api/server/routes/oauth.js | 84 ++++++------- api/server/services/AuthService.js | 69 +++++++++-- bun.lockb | Bin 747638 -> 747606 bytes client/package.json | 4 +- .../components/Auth/__tests__/Login.spec.tsx | 14 +++ .../Auth/__tests__/Registration.spec.tsx | 15 ++- client/src/components/Nav/Logout.tsx | 1 - .../__tests__/PluginStoreDialog.spec.tsx | 14 +++ client/src/hooks/AuthContext.tsx | 66 +++++----- docs/general_info/breaking_changes.md | 6 +- e2e/.env.test.example | 9 -- e2e/playwright.config.local.ts | 4 +- e2e/playwright.config.ts | 5 +- e2e/setup/authenticate.ts | 7 +- e2e/specs/keys.spec.ts | 4 +- e2e/specs/landing.spec.ts | 5 +- e2e/specs/messages.spec.ts | 10 +- e2e/specs/nav.spec.ts | 4 +- e2e/specs/popup.spec.ts | 2 +- e2e/specs/settings.spec.ts | 4 +- package.json | 4 +- packages/data-provider/src/request.ts | 67 +++++++++- packages/data-provider/src/sse.js | 22 +++- 31 files changed, 420 insertions(+), 232 deletions(-) create mode 100644 api/models/Session.js delete mode 100644 e2e/.env.test.example diff --git a/.env.example b/.env.example index 419004125..d4788e71a 100644 --- a/.env.example +++ b/.env.example @@ -226,8 +226,10 @@ ALLOW_SOCIAL_LOGIN=false ALLOW_SOCIAL_REGISTRATION=false # JWT Secrets -JWT_SECRET=secret -JWT_REFRESH_SECRET=secret +# You should use secure values. The examples given are 32-byte keys (64 characters in hex) +# Use this replit to generate some quickly: https://replit.com/@daavila/crypto#index.js +JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef +JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418 # Google: # Add your Google Client ID and Secret here, you must register an app with Google Cloud to get these values @@ -260,8 +262,10 @@ OPENID_BUTTON_LABEL= OPENID_IMAGE_URL= # Set the expiration delay for the secure cookie with the JWT token +# Recommend session expiry to be 15 minutes # Delay is in millisecond e.g. 7 days is 1000*60*60*24*7 -SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7 +SESSION_EXPIRY=1000 * 60 * 15 +REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7 # Github: # Get the Client ID and Secret from your Discord Application diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 6af0ed46d..f3dbecbd4 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -27,6 +27,7 @@ jobs: E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }} E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }} JWT_SECRET: ${{ secrets.JWT_SECRET }} + JWT_REFRESH_SECRET: ${{ secrets.JWT_REFRESH_SECRET }} CREDS_KEY: ${{ secrets.CREDS_KEY }} CREDS_IV: ${{ secrets.CREDS_IV }} DOMAIN_CLIENT: ${{ secrets.DOMAIN_CLIENT }} diff --git a/api/models/Session.js b/api/models/Session.js new file mode 100644 index 000000000..e1b9898bb --- /dev/null +++ b/api/models/Session.js @@ -0,0 +1,59 @@ +const mongoose = require('mongoose'); +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); +const { REFRESH_TOKEN_EXPIRY } = process.env ?? {}; +const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; + +const sessionSchema = mongoose.Schema({ + refreshTokenHash: { + type: String, + required: true, + }, + expiration: { + type: Date, + required: true, + expires: 0, + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, +}); + +sessionSchema.methods.generateRefreshToken = async function () { + try { + let expiresIn; + if (this.expiration) { + expiresIn = this.expiration.getTime(); + } else { + expiresIn = Date.now() + expires; + this.expiration = new Date(expiresIn); + } + + const refreshToken = jwt.sign( + { + id: this.user, + }, + process.env.JWT_REFRESH_SECRET, + { expiresIn: Math.floor((expiresIn - Date.now()) / 1000) }, + ); + + const hash = crypto.createHash('sha256'); + this.refreshTokenHash = hash.update(refreshToken).digest('hex'); + + await this.save(); + + return refreshToken; + } catch (error) { + console.error( + 'Error generating refresh token. Have you set a JWT_REFRESH_SECRET in the .env file?\n\n', + error, + ); + throw error; + } +}; + +const Session = mongoose.model('Session', sessionSchema); + +module.exports = Session; diff --git a/api/models/User.js b/api/models/User.js index 74cba3730..5e84d1545 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -4,20 +4,14 @@ const jwt = require('jsonwebtoken'); const Joi = require('joi'); const DebugControl = require('../utils/debug.js'); const userSchema = require('./schema/userSchema.js'); +const { SESSION_EXPIRY } = process.env ?? {}; +const expires = eval(SESSION_EXPIRY) ?? 1000 * 60 * 15; function log({ title, parameters }) { DebugControl.log.functionName(title); DebugControl.log.parameters(parameters); } -//Remove refreshToken from the response -userSchema.set('toJSON', { - transform: function (_doc, ret) { - delete ret.refreshToken; - return ret; - }, -}); - userSchema.methods.toJSON = function () { return { id: this._id, @@ -43,25 +37,11 @@ userSchema.methods.generateToken = function () { email: this.email, }, process.env.JWT_SECRET, - { expiresIn: eval(process.env.SESSION_EXPIRY) }, + { expiresIn: expires / 1000 }, ); return token; }; -userSchema.methods.generateRefreshToken = function () { - const refreshToken = jwt.sign( - { - id: this._id, - username: this.username, - provider: this.provider, - email: this.email, - }, - process.env.JWT_REFRESH_SECRET, - { expiresIn: eval(process.env.REFRESH_TOKEN_EXPIRY) }, - ); - return refreshToken; -}; - userSchema.methods.comparePassword = function (candidatePassword, callback) { bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { if (err) { diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index bedf827ba..fbf85bec2 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -1,19 +1,27 @@ -const { registerUser, requestPasswordReset, resetPassword } = require('../services/AuthService'); - -const isProduction = process.env.NODE_ENV === 'production'; +const { + registerUser, + requestPasswordReset, + resetPassword, + setAuthTokens, +} = require('../services/AuthService'); +const jwt = require('jsonwebtoken'); +const Session = require('../../models/Session'); +const User = require('../../models/User'); +const crypto = require('crypto'); +const cookies = require('cookie'); const registrationController = async (req, res) => { try { const response = await registerUser(req.body); if (response.status === 200) { const { status, user } = response; - const token = user.generateToken(); - //send token for automatic login - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); + let newUser = await User.findOne({ _id: user._id }); + if (!newUser) { + newUser = new User(user); + await newUser.save(); + } + const token = await setAuthTokens(user._id, res); + res.setHeader('Authorization', `Bearer ${token}`); res.status(status).send({ user }); } else { const { status, message } = response; @@ -61,59 +69,47 @@ const resetPasswordController = async (req, res) => { } }; -// const refreshController = async (req, res, next) => { -// const { signedCookies = {} } = req; -// const { refreshToken } = signedCookies; -// TODO -// if (refreshToken) { -// try { -// const payload = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET); -// const userId = payload._id; -// User.findOne({ _id: userId }).then( -// (user) => { -// if (user) { -// // Find the refresh token against the user record in database -// const tokenIndex = user.refreshToken.findIndex(item => item.refreshToken === refreshToken); +const refreshController = async (req, res) => { + const refreshToken = req.headers.cookie ? cookies.parse(req.headers.cookie).refreshToken : null; + if (!refreshToken) { + return res.status(200).send('Refresh token not provided'); + } -// if (tokenIndex === -1) { -// res.statusCode = 401; -// res.send('Unauthorized'); -// } else { -// const token = req.user.generateToken(); -// // If the refresh token exists, then create new one and replace it. -// const newRefreshToken = req.user.generateRefreshToken(); -// user.refreshToken[tokenIndex] = { refreshToken: newRefreshToken }; -// user.save((err) => { -// if (err) { -// res.statusCode = 500; -// res.send(err); -// } else { -// // setTokenCookie(res, newRefreshToken); -// const user = req.user.toJSON(); -// res.status(200).send({ token, user }); -// } -// }); -// } -// } else { -// res.statusCode = 401; -// res.send('Unauthorized'); -// } -// }, -// err => next(err) -// ); -// } catch (err) { -// res.statusCode = 401; -// res.send('Unauthorized'); -// } -// } else { -// res.statusCode = 401; -// res.send('Unauthorized'); -// } -// }; + try { + const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); + const userId = payload.id; + const user = await User.findOne({ _id: userId }); + if (!user) { + return res.status(401).send('User not found'); + } + + if (process.env.NODE_ENV === 'development') { + const token = await setAuthTokens(userId, res); + const userObj = user.toJSON(); + return res.status(200).send({ token, user: userObj }); + } + + // Hash the refresh token + const hash = crypto.createHash('sha256'); + const hashedToken = hash.update(refreshToken).digest('hex'); + + // Find the session with the hashed refresh token + const session = await Session.findOne({ user: userId, refreshTokenHash: hashedToken }); + if (session && session.expiration > new Date()) { + const token = await setAuthTokens(userId, res, session._id); + const userObj = user.toJSON(); + res.status(200).send({ token, user: userObj }); + } else { + res.status(401).send('Refresh token expired or not found for this user'); + } + } catch (err) { + res.status(401).send('Invalid refresh token'); + } +}; module.exports = { getUserController, - // refreshController, + refreshController, registrationController, resetPasswordRequestController, resetPasswordController, diff --git a/api/server/controllers/auth/LoginController.js b/api/server/controllers/auth/LoginController.js index 0c7cf271f..9c3b556f6 100644 --- a/api/server/controllers/auth/LoginController.js +++ b/api/server/controllers/auth/LoginController.js @@ -1,4 +1,5 @@ const User = require('../../../models/User'); +const { setAuthTokens } = require('../../services/AuthService'); const loginController = async (req, res) => { try { @@ -10,15 +11,7 @@ const loginController = async (req, res) => { return res.status(400).json({ message: 'Invalid credentials' }); } - const token = req.user.generateToken(); - const expires = eval(process.env.SESSION_EXPIRY); - - // Add token to cookie - res.cookie('token', token, { - expires: new Date(Date.now() + expires), - httpOnly: false, - secure: process.env.NODE_ENV === 'production', - }); + const token = await setAuthTokens(user._id, res); return res.status(200).send({ token, user }); } catch (err) { diff --git a/api/server/controllers/auth/LogoutController.js b/api/server/controllers/auth/LogoutController.js index 227ff6ad2..714a6466d 100644 --- a/api/server/controllers/auth/LogoutController.js +++ b/api/server/controllers/auth/LogoutController.js @@ -1,12 +1,11 @@ const { logoutUser } = require('../../services/AuthService'); +const cookies = require('cookie'); const logoutController = async (req, res) => { - const { signedCookies = {} } = req; - const { refreshToken } = signedCookies; + const refreshToken = req.headers.cookie ? cookies.parse(req.headers.cookie).refreshToken : null; try { - const logout = await logoutUser(req.user, refreshToken); + const logout = await logoutUser(req.user._id, refreshToken); const { status, message } = logout; - res.clearCookie('token'); res.clearCookie('refreshToken'); return res.status(status).send({ message }); } catch (err) { diff --git a/api/server/routes/auth.js b/api/server/routes/auth.js index ed7d1a68c..1ccbcb34b 100644 --- a/api/server/routes/auth.js +++ b/api/server/routes/auth.js @@ -2,7 +2,7 @@ const express = require('express'); const { resetPasswordRequestController, resetPasswordController, - // refreshController, + refreshController, registrationController, } = require('../controllers/AuthController'); const { loginController } = require('../controllers/auth/LoginController'); @@ -20,7 +20,7 @@ const router = express.Router(); //Local router.post('/logout', requireJwtAuth, logoutController); router.post('/login', loginLimiter, requireLocalAuth, loginController); -// router.post('/refresh', requireJwtAuth, refreshController); +router.post('/refresh', refreshController); router.post('/register', registerLimiter, validateRegistration, registrationController); router.post('/requestPasswordReset', resetPasswordRequestController); router.post('/resetPassword', resetPasswordController); diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js index 069ece7cb..556603e9e 100644 --- a/api/server/routes/oauth.js +++ b/api/server/routes/oauth.js @@ -1,17 +1,15 @@ const passport = require('passport'); const express = require('express'); const router = express.Router(); -const { loginLimiter } = require('../middleware'); const config = require('../../../config/loader'); +const { setAuthTokens } = require('../services/AuthService'); const domains = config.domains; -const isProduction = config.isProduction; /** * Google Routes */ router.get( '/google', - loginLimiter, passport.authenticate('google', { scope: ['openid', 'profile', 'email'], session: false, @@ -26,20 +24,18 @@ router.get( session: false, scope: ['openid', 'profile', 'email'], }), - (req, res) => { - const token = req.user.generateToken(); - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); - res.redirect(domains.client); + async (req, res) => { + try { + await setAuthTokens(req.user._id, res); + res.redirect(domains.client); + } catch (err) { + console.error('Error in setting authentication tokens:', err); + } }, ); router.get( '/facebook', - loginLimiter, passport.authenticate('facebook', { scope: ['public_profile'], profileFields: ['id', 'email', 'name'], @@ -56,20 +52,18 @@ router.get( scope: ['public_profile'], profileFields: ['id', 'email', 'name'], }), - (req, res) => { - const token = req.user.generateToken(); - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); - res.redirect(domains.client); + async (req, res) => { + try { + await setAuthTokens(req.user._id, res); + res.redirect(domains.client); + } catch (err) { + console.error('Error in setting authentication tokens:', err); + } }, ); router.get( '/openid', - loginLimiter, passport.authenticate('openid', { session: false, }), @@ -82,20 +76,18 @@ router.get( failureMessage: true, session: false, }), - (req, res) => { - const token = req.user.generateToken(); - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); - res.redirect(domains.client); + async (req, res) => { + try { + await setAuthTokens(req.user._id, res); + res.redirect(domains.client); + } catch (err) { + console.error('Error in setting authentication tokens:', err); + } }, ); router.get( '/github', - loginLimiter, passport.authenticate('github', { scope: ['user:email', 'read:user'], session: false, @@ -110,20 +102,17 @@ router.get( session: false, scope: ['user:email', 'read:user'], }), - (req, res) => { - const token = req.user.generateToken(); - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); - res.redirect(domains.client); + async (req, res) => { + try { + await setAuthTokens(req.user._id, res); + res.redirect(domains.client); + } catch (err) { + console.error('Error in setting authentication tokens:', err); + } }, ); - router.get( '/discord', - loginLimiter, passport.authenticate('discord', { scope: ['identify', 'email'], session: false, @@ -138,14 +127,13 @@ router.get( session: false, scope: ['identify', 'email'], }), - (req, res) => { - const token = req.user.generateToken(); - res.cookie('token', token, { - expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), - httpOnly: false, - secure: isProduction, - }); - res.redirect(domains.client); + async (req, res) => { + try { + await setAuthTokens(req.user._id, res); + res.redirect(domains.client); + } catch (err) { + console.error('Error in setting authentication tokens:', err); + } }, ); diff --git a/api/server/services/AuthService.js b/api/server/services/AuthService.js index 52380bc8a..a54053f05 100644 --- a/api/server/services/AuthService.js +++ b/api/server/services/AuthService.js @@ -1,32 +1,36 @@ const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const User = require('../../models/User'); +const Session = require('../../models/Session'); const Token = require('../../models/schema/tokenSchema'); const { registerSchema } = require('../../strategies/validators'); const config = require('../../../config/loader'); const { sendEmail } = require('../utils'); const domains = config.domains; +const isProduction = config.isProduction; /** * Logout user * - * @param {Object} user + * @param {String} userId * @param {*} refreshToken * @returns */ -const logoutUser = async (user, refreshToken) => { +const logoutUser = async (userId, refreshToken) => { try { - const userFound = await User.findById(user._id); - const tokenIndex = userFound.refreshToken.findIndex( - (item) => item.refreshToken === refreshToken, - ); + const hash = crypto.createHash('sha256').update(refreshToken).digest('hex'); - if (tokenIndex !== -1) { - userFound.refreshToken.id(userFound.refreshToken[tokenIndex]._id).remove(); + // Find the session with the matching user and refreshTokenHash + const session = await Session.findOne({ user: userId, refreshTokenHash: hash }); + if (session) { + try { + await Session.deleteOne({ _id: session._id }); + } catch (deleteErr) { + console.error(deleteErr); + return { status: 500, message: 'Failed to delete session.' }; + } } - await userFound.save(); - return { status: 200, message: 'Logout successful' }; } catch (err) { return { status: 500, message: err.message }; @@ -83,9 +87,6 @@ const registerUser = async (user) => { role: isFirstRegisteredUser ? 'ADMIN' : 'USER', }); - // todo: implement refresh token - // const refreshToken = newUser.generateRefreshToken(); - // newUser.refreshToken.push({ refreshToken }); const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync(newUser.password, salt); newUser.password = hash; @@ -188,9 +189,51 @@ const resetPassword = async (userId, token, password) => { return { message: 'Password reset was successful' }; }; +/** + * Set Auth Tokens + * + * @param {String} userId + * @param {Object} res + * @param {String} sessionId + * @returns + */ +const setAuthTokens = async (userId, res, sessionId = null) => { + try { + const user = await User.findOne({ _id: userId }); + const token = await user.generateToken(); + + let session; + let refreshTokenExpires; + if (sessionId) { + session = await Session.findById(sessionId); + refreshTokenExpires = session.expiration.getTime(); + } else { + session = new Session({ user: userId }); + const { REFRESH_TOKEN_EXPIRY } = process.env ?? {}; + const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; + refreshTokenExpires = Date.now() + expires; + } + + const refreshToken = await session.generateRefreshToken(); + + res.cookie('refreshToken', refreshToken, { + expires: new Date(refreshTokenExpires), + httpOnly: true, + secure: isProduction, + sameSite: 'strict', + }); + + return token; + } catch (error) { + console.log('Error in setting authentication tokens:', error); + throw error; + } +}; + module.exports = { registerUser, logoutUser, requestPasswordReset, resetPassword, + setAuthTokens, }; diff --git a/bun.lockb b/bun.lockb index 25f9ccd96afd66c5e5cdcc28667345dddef815f5..a83866beb58f8a008c96e3f6a3e13f71c7c3c88f 100755 GIT binary patch delta 102447 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 delta 102733 zcmeFa3!GK+{{O#bPkWl3(uH&rC0&U^W}2y;>86XMh(a;dR8vz^Gt+IH>={HZAxl2F zgb=xeBqfqTlzWKWjuWDe+u@v?MEswxwceZI*Vn=6=<)bJT8}>S`n;c?_vijupSAXy zS-aO?Zu|AiZLd6{>DVH+V{3$Rr{xzGV^5_uYS09Jh}WSIjl}r; z@p)}W4);>AYt{w4PDGBm#nUH@7?)o%Y+pZ;(jsxj{$crf#Rd5jN<6Rs0b%|grr)FL zU_suL32M*3jITF+8*M=P$h^FXZS#x0W3knbU(RF?oYbyX)JGS}YU9^jGc&fh?eyI7 z1zv8^=!v;S#d)bDsGa%6ZG+WvbfYlib~>Sf$sawTuqe;-zQ9(8<&;}c>wlCbg#uBVX=9|6N~a_ z{}T+ve)!!C4<>X(VUb4oAse7)nuUA`<$=mZ7LF(>%Ae3WZ)D!^$)j-`uj%I)F($WQ z9C`@Dsev1lTilk$jP}b?>nFAWmHTb9A-cdiIV!ifWD=Q*@+KGOkDBhC-7;)V`&Qxf z%s|zPrlWFo52uq8wBWI|fyG$rJMD zCwAheEnyLZ%@QapC>+gN&MWwThAkQvoZLb*jJXqv^ONJBtY9|fsKSv2!Ss96v9&Ij zGn(p1&e5KCC|aWVkFo#;?C&>>wnz-<5O#P6stz1?Y-kTJ${mqc?0L(v#do5b#{L;$ zdU8FF%r6Q~i1f^m_oN)v`y}d*+s21e(_L*9bUb>|x_tanRwYJCpxW#@)J(}^4Q6{zQb+k5%r0f|Y zYEkmcOrDWV8vCcGwMZnl;G$_`)hw@s9cegv-G1SKT!5-woF{p0aT#YkdE#%%3Dd{t zPHbB^e5{x3$MC|!0xj{}q9TdcmVEpx`?y=!oOe(Ll4}H!14vkcs^Q}c3)K$K$JWxE zifXOTXUo>e2iq3u9ealM#Qc#ZI<>W4i`cx2MvWYy{3jeA4(x}f$pL%I>_>a4gDlnL zMWk>-UJ2W`#~z+PqV1>&FXE^X?Sg9Q9gAut+8A#~17vfl8IencXY>tw_{(zo!&Of5 z+SASKEHtJbwEqnTX(pw~wDAcjEOEKP${QGETPC7Yk zxI;B{Jx~(LjzGD*mrXw<+}-M6Ylc20qXy*luz<1=`EBzjj4JdtnZ4-Lu%QQ@7FPTN zw)Td+qN2j0wt3TP4-U)!99~;9^Z#rXw#VLtqjsSuhJ+Q~jIy7X%_pN8*zt_;oVyj( zfX+jMhMOk$>_f0MGoPFpj!4$f&_8c{eo0A!%SUnvBp3S{dZOEc>BGVix%>1mW0Zo# zOU?=_O7336v32qvU=@9QcG%#zP|f(atQ)0&g=*yHa^~%e=1rTJml#pvd5@dJD`*oE z+F1{i`}Nr3!U^n1-srpuy20?y92FM$ApYvvv!g@%KC}^bKhm`sBro6XvDJ` zRt43*`PhWe{}xnh=Yzbk{GTpBiSP>4bYj?(29v^V;sR82S{+r(x0B9ZTlT5>rJ~w4 zzGN5C(7s5)I>{!XxbO5RmXpd?t zs-w-(FBt}n)bpt9b*u>u@da#pwb6#QP5gLoSWy;Md}AV<`(Iv3lJ|CVSsR+$8K|y_ zbLg=a;ScA9Juk{DE-aXm=iPgL=yw~csk{bNN4WN@pWgcyXlgnCKPRJxaQ)fg&{Uwc zu$P$LhH4F*dtq473WiqxK3v-WqHt~8jVj*`RQtd`@zaQnr-2%Yjo2!u#l>MmhU+Y& zvt=&jl zzoQEqJQLLveh=43?t`uNH=Gv^%$slxP&4=eXzOdk4!;lAOnreZ|2I%ocL07W=bxmD zZ@D4#>pnkdf1+%3;Y7|q&l{yHNA8F*71xK3-BDHWCK=Sx4{r>oc=AnQ1HK|%4avjS zk~jrb1NvI}oA|53XUMOC{T6?9?0)Qe=(T~eHZL-V@n}O52BB*Caj4c*dsGeJ165Jp z=)7r7uvJlhF?U3{)4lz$)x#L78GXkZ{yeINJ%B3zB2?*DS^7C>y#%1Z9O~W?w(xXp zwYW9f2(68(LCLf3NQP1cC2t45SQIwk15`7823MJCR~NshP!0J~ zR3kTq@?`UoETPY2d5ft~J-h~0!Wk)Hg{zl@_TdkNbKM)Ro;cP^6Lj>_aPd7#y7DJK z`z1ds)_ySTX&?L=liq>+>hMd;!jbs+Y%YdcM0$=gp4-mkRrrL5!lA4%$9qwY$S*%T z`fyFr6jT-LPX$fUvmXxg--BwTPI}nCuFgS;&X0sA+dH&h)xL~s#*&|9+0_E~$*dAFPHPrCZ~1*-AvjW*DYU{PT~0iWr;QN`S) zl@#TcRfG*tzenX4I`f9%O{K)X<&mf$oVJm{Fr;}+@oacnu1 ze>Cap*ofldlIaukyi1-^$M_60orsn|D;#uoCR_T=D*vecTO5_F=Y2Y^h99*yET@HO z^7&Wt>BzII{7d%l8Ckx{e`Ws`k=s`--Le1okx0v)OLx?}Jwu*6*?^C1_i6Fp^t$@@m=)si0>pHB1NKe}gRZq#pcOwVdReeUr~k2$g0Pq()I zE603ZCwYh4v|A)tW4YN&U+Hjpia)qdX8LIZUh6mXt96_FEBn;EXH973-#oc~EEe&+V;IV)pHi3+>x|VNtD0YNUPfeD z#H~zAiO2R$^}I}IRlj0dMyw}RcHpBN{iS*Xw)i0lr5v!8sd7UUG&6Qu1l9_H+*u)F5jw7v# zpEDyPx&`Z0f5wFDSiGv|#o?4k34bxGieFxw5qk!!YtXEUQIwY+mY(A0Ov#A#$5IE# zH!&l2v8B-ttTkq(1%s2qhM}6HeueU3sXjVho)Mjm)!pA&oE=+lJk76|oDr+ZIob=a zRG0ENHDf6ct)le1%?gLL4h#Ay(io+w8R^~4aw%7)w)5|wke)sT(v$QbNpu~gYtZ+7 zIMX#j)G~`AJ!<$_h4I+r8lKk&&lHVPWOWUHCGabth9Fhl zj2ynNpEWTaoyn!TyX%~t(kY#ol4&)04y@_#m>7=?sp)4;ibt-j>6cE5$3Eop+Wp63 zTh;P+kal)0KdUGnxwDpEidibz!;<0nN z6lttd{FI`M$W!(Gl~Zu3@9!Y3Kija@mKRicX#;=d)OhqcV3EI*Wz@H!=N(IO6;}~w z!mu>3k-+)khW^TF@z~C!iOjA=G#*=UV7Ly_g1LSVOI5l#L#qUH9XpKu&DIHPIo~Y2CuF4Gfu$z>`#A7Q-Qu8A&<%X2ZDzf~}6SHIO4hb6?)hQe)IK*FB#$r0e-+}!55I?It z9yz6%Uy5AR%wJg^kG3LnSIQLkU#V!4n7sX?@1J(N!SAGGfZc9IFRy@|ZRk+gt6Xe6{ z8RTQfi@egx-!Us5t=-!525F8Wqgwl=7sq3-0<_+k$w?W}8f|#c?5~=f9U0a}XH(1v zX!8pD`xTbXLZ)MSM)bJDNOPS}ItbaxC>O9J{C5`dPE%k+dWHQl#q<{z~MMBm5n++uv^8}U=0t_wAxy>^GmOY$IfESYC3}q>2t6&oj>lJ zu?>*m9B?VC8T9r#gVXzQ;R{Zzj z);U`PLw+o0PS$;Bfkq>|nhwTNuJF2h6_&afb>-DqcUT&b)L@kK)Lx!phx=lMgQ0d_ zjCG2-7=4#m&mh*Uqpr-b$n=i>N*9l;=;-f2_RI3K=EY+}C`)${DXyX_RbeqWWi|Yq z@{Cv$5^@4d7upgmHJ@2vE!>Z#W<>p*t21JM$6_wot#h8C6x_l!u8y8=R=Ck!how^J zc1cF`by;p_>y%DaJJU?~Pm5y=L|qFm%jvgcsmveK(?5b}+5bqQnYj1yJ5SG!o=2>w zzjIc0bS<%CHFeSNiN%9hC(iybR!S_`j$#iGW3rew-KcEA(!%9>Q<9P1kcwsfwAG&g z345;_hzqgQdnWISjMziw%>v+tY73U8Io#0>XE>EFxH*c9%JEBYipS;yW{AE;B0c<-x5Q)ZNFM|YCOl;JF2=tm%qYNgZB?sSesM$69lZ_@)Y&41_eX7h1d`{YqT&Un!}>b4vPHCAiwm% zcY@RQJ-~_!7iC3T7QkuA+xH5_Z7SCAut;X*O)NEoO9Xr9frFDL z0avTY>4W{!hvKpOfEvtj_%>l_&9arUhc~Bj!Il&qO01hdbHudXf;AqAp6>5h z9*-8BZf%S7J;N_u5sxiCBW$7RwWW~6MWU%E0Ldl;b8g!?QmlHUyVcdU#@j~T|kq1@5Sh_ULYW=G#5 z);VDH&hosz8ro=rSlr)PlAXSa7|Y|QJv&z8>~QZ1Hmm4Btgt=*AQp5vIA5ydYA94w z4FkzQUBltiQ#PplfF~JSm+&n*Pbv$;?sN~k8bLe5L za4K|D_sJ-K$Le^b)o4HK@p!akw5sU5Iy?OxVw{9OwL8*kjK2eyvN38fE|KH%{nCnf zWOly45_vY?-%$~dW{%~?)8F}ccI4`@f&ZrfI=DPL+IF1hjZ{)}4lz2nJUdpSAlaCT zTQVYD3jCE%#v_jx_&c7A$Mzc^F1g@VF*0<#U;0!$_9&2@)FwGHA(&(e&moo@H0#X? zLA~t@`DJcU?`6dDg4)_n42PI?$08n$6)t?N$oz@^%BSOzBPaPgo{q=dq~r;%o4Z#h z`K8aqBL@`uE0Hlp{*Gtj(ff)#?<^G?*|*p)UCRwclc=Vu>;4#6P zPAMgR>AHAy6ZTYp#=7k2;K`m>s95Bg$$r+e@o0xBo>v|uJw)sRzw@)%(X6Tbej;G= zi47Nv{4muoeU7tXn&+Jsy~PrXpx>t ztcP25JvZOPGVx${e<~yTH5MPXCS}KtJ}2B@IkD$u#3o?13x-}dr8i>fY$Fe+&pS#B zZbT0Fi%ujyAV&uh%k)1J?}e-#*~;>U%JTj3ofDOYf%kDidg@@GuUPQRC!&fVY&>e{`Q5=)0<1fDs5 zMr>f2jfku0&s}(#U;0Ws_8hPiE_C+#%&O(#KAZe38%qa-gQ#ohOe}RiyhOLF2$D5| z>=xH$Rw4E5V4d3znEBfp6`2pa+xr2k+aVCcf7`j5FqCzmFrS=^aaVLq%6va z6kg!3+z^jF2s|Qi(X+5Gu(}482CV(eTYT&xV%#67Pn_%D&kQaqITt23 ztV@W6JIWej;l@_&B8$aF5L4!A!QIv)SY3kaNpx1&c{WZy?{&jsdwQ!=6(T45JKye< zGAp?H9dmKG?FJXL=rpV@e&^>qr4z}bRPKYOWkl;-l5A=}Vm;x(O-jVS#Ls#=9(xOT zIFO~wEmYd2$%_K}SoAooaNoU|Sh(*-E(^{HuBwHX`77UvM^^!Q`m5gIL;Y;pNl{gU z&&@NibgkqTW^qRJd91$LJ7Zawhbt#+^h~VawmJ4BF*}D!GGgCg=}cfra;0f~1#2tV zd9NeJp7UU*Dn$C}B#U*uGTh+Ua$e7@ig6oYT-i)L2L(>PClw(>1SRS_9HxVuEYvY3-+kUE7$p1AIGEL+z?FflSOf`8u|?}wi=#CoqJO_WhpM@ zK<*inmd}YB`wB}_L$k56ZccVWpDi!I(rFNkRqQdWxGY!k08@i?LNYDVYk{BjNjx@t zLHHRU?B@qRvpU|A%%FLmiFM|W)o#KX{G-+J)^Nw96h3QRiluWdEOR|pW>_r^jNN9< zbt#AQa14vjTC=ia7ZcM6v(vHmpTi1no1#@0YVI^7XAo1jgDXSqRxH)Q8O3M4tytkE zt4*x=?aAR)yQgDm5wbpB%80&%#hI&hb4R!wQ(XD$3=WnCC%k=HiZv{#wf3T9KPfAc zzsS$}k_Sb=&N$Z$KBs<%r7^GNm#@r-9ebyB#8q5Pu~^3imFtnx+B^Nyui~+4cO_So z9{-$xWi>C#h|a^}n)Vgfw7dM$f5u}+-TmW|=*q(>hLBoE!^Ip4e!ta0yUz)Vr4v4L*IINlfDxj_tf3E$tq!VIA{hTKauSD`jhD z6^xVp8LwnVuOP;+Ev9CtZziUb>Zi@<$orEM5G*4sEr($HiLJs4myuR)-6cs+-SLmZ z(pkbMelE(d|CpvyIy{hE=lY;qh}93zM!~O8{)H7hsEM7kG&y#Re)Ku4;89NOpa;WQ z32)ejVX5<6cqr>StZXbc^tUsj8?bov$wQy!%lxcw`R&6)e(AUI=-`K_Ke%jNMJ!z2 zTKdmoX;%x+xD=efi${-Lt~q5xh?NpMkz`gGABxsu@iZ)v9os?d@PIQfkrpfbl{@3H z3sxkb1ZuPT2x}}ZY{8dj#0nk`d%)N*8joV>)CgBam6e{?8;hHni+I+Eb!=EPw`sGn z`1n@NvpZsEz*)l+GNPj&2`({DXUBdZmIV)giPP~>E)Q5-thp$hkEP~Q4^Ob)est~* ze(%+CRdPZr=4Ga=a-}IL=SHSJ=60l{#3Q#q=CUFGBRLVTQBK#a2v@QKIES+h#?iR+< z0h$;tJ~X|`6UkFWOKSj@(!%TU#aLOv{5(layGpp^4tO#=PlL1KqRd#z^2}J|(ME|z zUhpe=F9ki25G6DvLy=fkVx>&kY{#^+wr<>KUM_U140}77HD#soK zjSQPXaQqfkMV!dNA(ed+LFEk~=op~>3k;OSu`8_!&ka&T-H&()VPC>|gml6s1Wf}Q zR&Yo)1lrVW>6m|U~ssyVUa2^MFZmIw~W+lAVqe4c1z-*P-gmO9UNK`MoTIqq3^L zSCY$$NZ|OYyf}VMRp4uZSEy>h8)i$DzR_%{Dte0`{x(5}RQ5ZbyY8$=9rtOgNG(mU z_cuZn!iNMoY$oW~m8#&63G&-Q&>#xT(^MH8fx6jevt*)})eK7&-N$UHqSej* zEvoWr<6jl6kE;C*Eq`Uz(rS|8x%1mZYIjto=H}eeoTZAkGHq?zMvz0A;$Fy&9Gp-c zM;MmMZfCYs_EBb6)@txhmL9kCUsFwT4t{d&YW}FZ`>;rDch6ywTEUOtEbKV0rW=>)Bs(t{e?8TY6;`y2tpgRQVqu zT`o&QzeF+;sMv$%uq<@&q{{e+*_E|E{8hLX*Bh2zS%o$lm#SlLq3Y{6Gmyc|-wS2tU#{?$RXBpRVA z=0HoYtOvl4GA`X8yC0sK8SIS_-*nO?09;{+en$ip{UG>ZF=s zyt1kuy(ufzPA8Z02q&2ey1+6>)#6!ZOBKDu?8>TqvyDq-Ux{kOu0d6iGo6R3$Mem; z5!G>{TA~VXK~=y)OZYviEO+ojdZ*==Dtfo+V$=IfmzXA&T4b5&a?^)RA2D5Jx*F9K zy=eB!sE*279s3>QQjNsBrhhdqg{OJ%DdL8vDo83K?{%c@*8<48t zK4w=|?EwSfN*cruHE;+j|I<;Ga~7%whPWFG5wo#i)`l zZ3|;VA{~M5vrrI z3LVT30%KA6 z7Z@)zEkbovR^=~-2kUPN5oMfa8O||17v-OKp6P|CGG2sgWG+MXiS#;4zY*1GcN?nh z?0(}9qALF(R7WKZ_MZT#;N>_-SC~F*y3+I!R24st^3QvU9~zNoP~}@IXsYP*{7^$S z8kg#9+Jve>A5;nJCxR7#u!1evb+C7!O8VZ?E2}E}!MIe>R9Yuq#Wbqca!A$CbX2GY zKh%%|^btrI>WP}FWev=hD!n19p>1MZD&7>;+_y3=RYixHU0Kzk&c>yRCc5%d1$9G} zv8UO`E0sg4^j@ec>TmW*sQgbxbx76lLB)p~S6ljAw2IqV zX!nIT$Rzn@RPDI~RX^@R)!uth759MI52AWD6a}iiX{h}4SN8JTS3gWuPHk+JU)Qt& zDt=%Z?`kQb34niI3;i&SxK$G)ErZ408mxNSSh7_4+nTmRRsT_@$Cw^#nu*Fk+w9J! zU5s}{Rem?q?x+ql5ft1LK%c$7s4D7*s=$-X9*C+Rry3t(dM3(0Z@Afcrejc*GuG_! zrV~-sGuiBEY4l%?r3Pl8vJ+;XhpJ+|(I7s{_@!pgHv39cJ)C3q)s}vZ={)1tn?2v` zn^lok&RwV~y4M`;H;1KWFEd?^%I^`=)u=}52~<6M2314X8GqjF7foMA)lhvsfXYd1 zu!J|zF@P>CP!*7a%CV~!>@PH!OSnF(3_;~`w)sibpb@D2Mw;eXx|FKCaiN_EBC2q_ zIaF5hLbwVnvh>QT9!xPVmH#xerApUpCThqG<3CsC|K}jIbz3vjGG1tUkrnW3s)}ae zrv_h&szI|Y|K*lns)k*KR@M1;t%3QdGTel!pqo)0zeVMG2R~Hs-Io6z%l~Vds|qSR zr~&tqQF^}>w8RRktm@%|aE;WXmM&ET9!J%I3USwVvTcJ;VXD}tg-zxDEVhby#nN9x zm3;%MHT{;^Z=3x$R7>b1;~%4H&fig$^Of1#P#tLa@%U>%EkGJxs<@iGO=a(EypHJs zs0waic4Jg!9*U|XEsVE8bx(0DsvG0uQFWjfs-u_ws3wer8T*i+g8QMWc!233%TQV6 zHyFMTI>OSWs$itql~p4%*7%Q_s05U89JYE=gsLYcsD^qvs`OG+hg1cYp=#&_sQfQ9 zon`zovoANj5>JDu?Ug>fw!+E>(t`%$BOc1!hatpgWA;iK=0X&Aty+ z{`*arpd1NL5jieHRp9b4AyCcPT5M%pXIv`(7f_)M#w)8Dz7Z~c+tQ^f|6Nq&{S{5q z_XK=ML`gF zU#hZ4;ObV?(xu{QW>;2S1`ag-J2X5een$qX`9l@`ANlN@R0DKwwL~?btx@%~4XTya z4%NVRFwH{c*U6L@r-G5vm$qrdx}(b12UR}3geX1`RlZY9PfZY! zz;H{Ks^C0Se6(???0i(`_(bDU`4?MyVlok>oofl@s2VU6RRI^F8i`q`j>;;2v87*P z{+F4bRHxK@OTQ6SM{hH|3#FcfcP|mOXh{gXWoEBH)$-M5KY^-%wWyw%y@9F$8&Ng* z9n-&|D(?eS{#(ud6jg^lM|DV3sNeg7hzj^8s=KhAW~X4Nf(WYTsd`0JpK2SUs-Ovq ztJfOU#^1qsCaMceFI4$^qiRThvrj@*&M>sP*56nn_0V!uJ)VWCL6@OAq^j@=REz0q zR0YqqbgBHWHGUnc6aN-e`4*!3Mezz$9a)2FvA>KqmBXtHnf4Es1K@0jUPKCaV6|GG0qxjG}?r&l2{x1gUsEvn#7+ z@*uco<`7gB9cq43*)7bL%5G)a#`JJh4QZ<{Mp5JlOK4|$l<}icRg__NCaUANsMcm2 ze@$&)%O_QRCzvMs5m5yL3=BkdNcE=}m!j&yY)ik~>?=*LGVYr_*X(Ohjo`IrUuWqz zpqh#Wp`Gw;EYwMogp3M#)Cs(gAWU0MTGh5Mqar?%<-XhIq56H&&- zs4^UE_MxV&QDr>b?DnR|qVmr~RY6Bohg5bJs*SW8+6WzH=~Cr8yE?+JCCVf9nqqn* z;L11BG!Iq#^5yT^_Kh6uI`!qxE#y-G)3Ga6*%PdcLR4)pGJmP^7o#eBs@c;lT`K=` zjQ_=WB|Ri){JEAORfcl2r4jdTU)x(}8kVZQi_Mm5Y_3Ap-m6iyYp&T2mH+j|Z$NcI z-(u;vp*p0>f4kY0G<^E9hy=CoZqs{F75so@kgDRPrVpYj@F7c=%3gu0oJUakKWgbx zmG`*mQ>gr(uAUtxJZp}XRRug}jxV6HUo`t=`MaIx+W@{QlcTa~v2HXjRef)nzJsc` zcTrs|H=}Cz7F5TsRQ_8@*8|gUQI)r|I`GJcs)2sg($PMcKLh{KRt1kyfCi=lRRvF|K-cynTm5S+d95&qRO9o!*}GB|`y%No z?j`e+YT>+Ywp8hFm@U;J{u`>B@Gms+a`*>81?nqGG$H>+)!rS(zeCj^-TBHdVp;_i z*LRnw;+m+Y@+eC`#`IWJ^<%Q>d*+|BT?lWm9WSdRF31#LEl#-hl!>|s0uDc<)?fq@H|Vu z096BKS^8|V=UBRLe4gdCvNe?wK^ zCRB%1J=u(^VIN!i7E9liYQ(-U|1T4k;VV=HeT}NZZ%n^KwU#4%eALubMb&_GeVjB^ z#nsHNtm4(pzlNp%QWFB2ySk_{)HBCjsnQ#et_B`xe!r$_Xk+}O2cgQ}#PT-_^Y`== zQGqSYp;egR?MhWpYtp4{%ugD1Loee`j|(n~qz6CrRd-%HbEvH9X(n71W}C*%PpW)f zPz`l=arf>ZP7)$s4@>T8dOWHb>nk@?P3=HSKiT-Nsj3-le!Ef)*y**nxzG|g+Z?6o zZvLr}mhRD0?N5luT3TgQU&g@?MlZ2+sS3T!>|LokbtUO+&)$6Vld8NM&90=0V6bkr zgx{ko@HX} z=^9I~th(uYBSC@+-e~%!IZCyaypL+RZ8k2I|3}8RqN?B%OW&1h`oAQd%^-M$rt(tt zD2-#NBC4o5s)B2n?rRQGRZz?9$}0cbmcE~*OXXh|O?5S{wDsS>l6R#lrlF-vmHj|e zys_y)sJeIrs-v>1Kkbc6Wgmkoe}~%q;qs73EVxNK)|{jolul;vN>xY~ORuad^f=>E z`E@hxi7NegORuC6PP)c(Y%or;bgBFYm@U<68-%KTXP_F4p_X1*m2a4Fsr=419gZq} zgr!%~@S{bZB}lc4jI|8oj7zmBN{mlN)zN8YPe*kgm7%KmJmcr1%6Azm|0|4NiE8tl zS9?U5;W~is4Q@kqNY(R2W=mD@U1tB9s+@Wr?EaUM?AD|Ug{$JO1Xa|HphGIVy9|!XD&B*jl@Wfp_-(3s z`Vn;WQ~d%b5mev+f)1(dfigIDrP`=+gVa#}$V*94y`2j4?<+`*g6<9`YBc_X>h7cRt4H#et^38wuO5lZ)>xlwy7v{N zy{{mJr<`tLbpsjGaxzu4@~cPUvbBQtzJj#(6{NkdAn8>k-8Jof1!?aqNPAyF+WQI; zxAVGV+xrRA zg7o97NBV6N$Jo8EAnknxDO?JAC28*~Nc>JSX21G7kjEDKJFLC0AnknxY40mYdtX5c zpHJ?61!?aqNPAyF+WQI;HwJrOLE=w5^*furuORJx1!?aqNPAyF+WQJp_*8oDD@go} zi5}DMeFbUnD@c1^L9$m%cJ(Tf{!DLIuO5K|uHSL(eFaIQQTf%Ry{{l?b4{(SaoPI{ zQZNX6UqNCu=(E`Wk6uBlq5dRaJzDOry(&`TmW@d5>jwLg8m`93)V`5a&z(Lp^;DOB zHDr}Y)N|GHAS*=1=RvA^Zl%bWxgs}3`tmJmuFfb({c9kTN8z)N=bjK*D>DAmNZ*ti zZtHcC)7&%%=yyGtYI^Rjzei4Wt>!^$d;;OsxYIv@Y!q4i0Gal4-#kF3S=R!FE(O$e z*S-tLybiGQT|hn8`>%j40*n6&XyCpPn0LLhya#CH?tBl>>jpsc(X_3x=ZZ$tw(TNw z@5AL_x9L7y7R?9rxF68e&AA^i_(s4sfo3je2_XF@Kn)+z+zs&oD+E>vv~;nn0b_0k zjK3Pt+C3~#e*vKJT)<&2e=cCHzzYIxU4v@?({2IGxCU^fTPM)!RzO<^Xz!*wz(#?0 z1&(&D=K*Hj2ADk$(80Yakhu`h`C34RyZBnb7J<(MI=YS;_<6Sj=3fWMcGq4H=yeBR zCo_@K+4a5wuw7vB4S+808-YcO07K^ka@?Ks0fX-Z)VLAQ%?-H`kbW0nl|T;{y9uyD zU=3>`<#<>7X26)c0h4bA^mY{j_3r^RUjXRqCM^K071$uq&o#XTFl{kl<}HB!ZoNRO zdjTD81q^WIw*oc_Y!*1#wZ9E8>psBT+W>>yCV|ZR0X-H1PIGe>0=5Wj6By!hZU@X; z0$6xE;0*VrK(7Y?gYEzfbqnqQY!`?w0-WUrECMWA3RotP>r(Cn41N%hcPC(kTOyFY z3{dwjK%UFJ3$Q|9jlgJE`)bCd1?tQFWGFy1v?44Ae8 zFmo}W(5)9}^)R5ry?{xs{9eFDfz1NNuDw?Btd)R;O8}GIms+@)j{pYgl$z@9TngAC zP~$s{rGd0cN;|1-1(`eh83o`40gWJqCC|pxiZB4j8-| zFk?C3e78;@{c%9s6@Zy;`U=1bfp-Nia;+Z*jHv+3ei(4EdsCqP6M)Vu0hhXqR|3`w zd?qm4b$kRc?McA=M*vs2tpcr{0`z+nFvnf{C}5+&P66NbUIm!72C#S)V6OW{AoFR! z(8mDI-T4?`i$IOlfNR~5)qr`=09FZH?_!SwdaVVFe;hF1JuI+Ypm7D@CYN6UShNoC zg1`dT;0eIsX8|*w0Nm=<38X&ZD0(ZF9PXWd}519QF;7<3ZK>Zg0 zo!0>Fb{DS!tQGi7V6p4?G+^3`fcZ}Y?sHoOTD=75_Y7c(yY?BtMuD9IOI`1^fLSjC z7Ow>?bKeMLt_KWV2UzazTnE@9P~%y^!*0m4fO)R~RtY@fV$T73y$Trr9AK4uSYW$A z)e|H^)~`KzYKWJUHmd&t-xmjFSw5D0n^?D%wG?9$!!&A^%kJtD}eRx+E)M@ z1$GL&>UzHlnDsVb@vDFh?i+#3cK}0Q1H9qxd=0QgpvDHkn{LPkz`S<>s|4P5vDX2; z{t6iXI^bRRu)ubK#%}=LbNO!o7QF{}L12?>un{o$Z-5ya0Ux+^0_mFoZQlfJcGKSk ztPprt;A7YNEx?%f0khu%Y;|u6)c*j``E9@_?&7xrYXv?N_{?>D2QckJ!2EXrU%0IT ztu_Pt>FW8VOZgjMqd?x@0RMDL1ZI5%sJjWU&E;+aWPS`-Bk*rm`+dL`fywU!zHt=- z^R@t*e*pN_P5J=PYb#)bz)sinL%?={nI8gvaO(vY{Tswk1WErCGWR1$G{wCyvO=WC$B?Qi?y8R=V?Kjy6N#m`u3I4WKZh*b0@)|UeI>G1 zWYAVfjTCpwR>-t3Akn`=YNoh>!JWuIAgj0&NvZ8(pVGjM0&6}6?B{BK2AK6FVDe{x zx~@VX^D98}&jIz^q|X6c1U3jXa817e%=;%`<`;lQZoNRSe*rrD1JKx&{{ygHV6(u% zuKkyQMcV*#zXUXOn*;`b4e0R|pqZQV6(IfJfNcWJUCuuND+Ct)6VTFqDKKU`V9>t+ zt=)ou0qTDPh;9QM<_2s7tQA-$(AK4V4VbnAkoPs+CTur21<+$Bpqrbs6ObMOY!m3=a=r(w5Loy< z;CT0?z?f9PpdSFe-GUzg^{b@Tm>W&yE@bY2R0c!lGR%H1B?Wt06#u*wK!3MHpj8^6 zZUivE}sb1W>p1DP6Z5d6#|**faX;Ir@2X009yn$2n=yeqkwraz)ZE` z47XmOS2aL~G{8_-o(9-1uvy?N*S;!X(LR8=RROtflfdBWfF9|95pGU8AiV})n?Rn+ zi2+s!EQ|q0yDtUC>}u}|m{kWbd0)U}S0RwOAE0?nz*IM>CSZ%e27&3WX)VCK z{Q)y;0nTyj1$xy5bf^uO;mT_RwhL?)NVxWO0E-R)%&h|`cbfzT*8}v}4{*MlvmYS6 zK46=`Oqa7iV1>ZK{Q(!bF9pUl01T=NxY#YI3#i`^5Iq2JsT*(rV6DJ1f!Qvl9$;D{ zKwdq-6>f<@s{;Xb>jUPv-1>lx0&4_(SGxgVR%5{A27tM)LLl=XK=XzG=O#4-Y!TQX zaII_F2r%zpz|2N~>)m>RUQGZU4g}11Y7Rx7~dLjlWNg+OL&K=bB+ zc=r=;46p z+yW0;aVGB}UNNU7g)diB?9kNX%8gX5ZgRBr)cpRi^ z#C;_)rUztD4kQ+Fx8y+T_k={dLiUNcfn6bMMV3K$7?RSB%BLL<$m<5E>6Qqz>IJCV z9Z=ilb_Z+}SR=5XtK9=It2bbB4?ta4A&}Vz(7Y$0o}1JYuti{lKm*tGc)+~AfSJbw z8oBiXy-omh=mlu(%6kE}3v3oR*tPEsSkwO}v+axgfL_m)|fM#w^A3%D4z&3&A zE~hVGg}}nTfR^q{fiWim2Au$C?G~H>s6PM@?FTr_4d@40E3izUtxGu(Fl`_p??k|n zZizsvlL2-61KPXX{(y}FYXpvVwNC=fIt4KKBtQpOA&@x;(0l+O!%fnqeT%>bfsU@} zK)}3H0W${zvfX-tUZ(*%oDAsf%1;Jt7uYP&#kD^LuxKz~?kRvAw@F~|5I~PXfNpNi zAVB)*fNcUjT+XS06#@%S1sw0b6c}>`V9;rR-fqEZfcj?wqJsf_-GITVr=^_W?v(U% zDMLt}HZ--m8#yHP1l@uNv>Jwg-P7?O;Brp~Y!p}{aI&j?24L1%fXQb72Du7>%(DT_ z&jg(2CY?#ygWWpG5Z81l$@6llqWYCoafVwj&}%sU9fsjQ)RhkdY!}!paF%O-7GTi` zz}&L{xo(rd;E{kHX9GsKIcEdX^8nif@?1_XV1>ZKT)=4erNEd`fI-6n`EJ2*K>g8x z=m@|#H(*5SX&0>>ow|JO=qiU^82st*fb>qoYo8iCmJVK0D#{;UlshW?e@yu*o-+MX zXLLc~aCzweHWgERH?YL+F7S7jm)LDR`JEM8y@~(Lu>R)8BzE(B{%XZqN89-}2L6@t z1Y6(lmIxNoFW{fZSpSt;^Cwnp_3CT+8HPWtGFx3AoOYH6fe`k3KeVy}e>B;Y`*y?TapN4h$xnET&O-b}^7JMs6 z(9+xyg+&uQzRLB_W8pTr2P(D>C;s#v|9qqqf8J34yke_2*e3Vf@E0cj(1O#ZQJgn^ zN?sB7Y;Awq>3%=6ZL$gl>X240@PE#N@!t~_t7ja+|6cN^_BgCJIns$AuTtWFWcZaQ z87g<=w=u^5w>kcg{51c+zty|1F^S|&!X6rh#GV~~ee>a-9R3>AU&a#HW1|q9|C;+f zGzy75Rk77;ACvdc>JR_-sy}=9b@iOjryD*gl_g&95xg-|2Ky2I8dK(p`6Ek;^76a~ zdj533{s&oj|Mv$o2aW#k@+Nj$PySeA694@W{;`U!UR{57=X~z6F~MiNykb5N^DQVR zh6~SF;%86}V}IfJzh(4ar@{ZWl3%GqgV&vo*jOT|DGIv{qL!N%J6Hu{pF`)vBcQo!U_668}8%RB6?sJng<7|GdMAJv;pTnkHDmdusTD>;E4e{%}M3`L9l@&=Y;5oV4<< zQ~oa&eE$C}j=#0Q-|6?$uSx!|7FI4$Jub>CE-aXm=bdu?Pj|9^oU#7@c;?-ZH?iA# z^2ZwUV<)9H7Q0{u^Z>fIxwpz;4@#k>11N9BF6 z=;t5VwED#od~w*%KK=Y(j{hUS#E-Y%e{#iEFXO*wDzGJnXxT+(#m;% z3)7yo{?V|$5tSc;{0J*OTdKb*yeUpTZz8X^JDE1&-CE$T$_sY&!h(XyJUmOS_~|ac zsIVZhEBGg}Ef!0npMNILqTT$2_UDz9{ojpF?5T>a z-bBG}dM5pwd;Ieb2WQc4oX=kw>YrC^Q{Mg>!?bVn8xU_;$Xwytv}j}fA&9PBXPIL?9Chu| z7jF)CbJnJ{jO5jDE7ztSRadTja`oQCMMo2r=~C9EHR8Jt8m~(`xQ9H{t4-$7Tprks z2p<}2iOTOl!rzUxGS(ROnJZtH)-v&pB^_o-O-QPuFGb}z9HwHM5~9YAFm?#6zOim!VEuT%R(K*8$I#{NbIAj_-)-tt%y`&N2$TYv! z#9wxko=w}Y^9D=GvZTXEYHCGf8#^4<%vdL5ZDE(V3W`YZi>u&>ThftOR~hR9(@?Y{ zTx%@H{MuvJv|_s&I|^3IST|!w!#=lWb~knmYNA zLhFiqnc%<3ROMX=cWLEvoQ%6#b{ru?rwqp+Dq@o-;Pg{EmmcZ0s!a>jhgMvV=DRsCM=yyk?Gh=GX@|-dMi* z^@SC|biR&*soy6MrkY=Yv3{`gjg7awC&Grqv@;Y2es1^+X$=!Y%x{wA9YFdxwhG}Q zV*`m#(e}YnZ0uy>)6K8M*eS4c%x|)>L9kCXuN+g1ol3kU+;oohrW!mA*fE6OG?)fv zFkz%39HqvF5I@4Te=%*pp{>n*h81xJNw3)oKiAlqun$=S4bX(Kp~P#GSNm0&6+4W0 zpHzNuoG0cMyqMM~vC5XuOv{=}a*nYJE$eXDDYnPVGCzH68*J=i^BW2Kn4P`}dMQjB zs#f^>u%_q&^BYC{16>h3&t<=qRwMFZHP`2*v=%7`xk)dv^qXqkbKHTymcTf|p~e;& zD}ZgXVZYPZc-XnFLPeZvNq1XPAxT$SbMG-W5mwA-9*QnDHi>v|SNmnoo<1bC#JrCr zO_3gXAF1YWEHU>I;(z7zXoWstY%=lv&2Oo(DX@*k9yB%;_9jfb<}#S-m`3=3z_G8l z+#IK4{nag@O`V?#l05HWOF9R8tWD}lW7?L+8GFRaodKKZQr1(%WJ`L?-1Px)im}z^ zUIv?Ltit@tVbk0I6;WzQPg&CWB+W3k#)`NAcCN8!%x@;F%q>t6qbzBiC0#_)Xk*VB zn+3a?0n+~a98Ad<6V7#CQbb~;jps||ekn<#jlFE{m%;Lly=rVWEET3L{B;=9=LMG` zn6~h@th_6TA7y#phG~ATBpeN6pWfGd*Bs{nGu@n5Xj68W=DlZ0K1l}>bo|ZO)x-}X z#0k2YP!)3tO$a*PH+Bv2n`~$Pz?g&8w+&4<9m+e8Fgo0n65eKm*8(rG%pV!M4t6Q5 z8~U-a>xqvtzb(dYfSqh?D@+}qPdMJ#C+2q}tS_tw`f1?DTDl3NpE-VRjyJ=)!+N4$ zz&NqI1%!jl?_cJ33+zy1+l<````UW^wXxe^|2Fn-V+&zB62Rk$YzJyuZzt?D_8m+; zxr6Y7vF|POBG@OeUP?1|C-Jvny;1!yyc+7e2!Dm?NHKOd@%M}+__FH2@gCscEOV-{ z#js7rbhoDh?x1fUPwYOzaw{;+*!?hlgZ>F*h=C@lX(cbzp+P%Ya`H6*Vv=P?>BaUu~o1}8>jm@tD_OY>pj6DH+zB(To(1VRVNxTAfHrmA4Q^cQu>1b*! zu?FKwgNGP=8uoy(W-!(F4B>cV&CPEute3GC#@505!*bD<#-1fU$oyKtRNixh)2s6^ zQI2iQ@pRS!o`OL~v^XXbvqvA@CM=GV*ECRn~(uOhC&eHLaPOZtHLb;kO_ zl=DNvR4cZhvCXh)uIZaxX2y|pDdqqx;$z|k#s(VO0-Ip$WMf-l8HCFSx}j4Ies~F{r-}0oU8p7eQZut$1svKG5;hCugQjjj<6#BMf~*Io~J9;NMqZGp9IqtE6>FKy4=4X)ZFy z{jfFM8)37I>0bXK7L7KVi;d}d(zkBYJDl!XPugfMwWN9^b%g1-%vgQcc1Cm)Y__om zu$x@_cPT`IM)#>P}K7T9Q7`96_hG43@z(3!RAHWmCP} z{5r$#F}A{39Jbil!^XP6RJ{gerLp6Pzf8XwlS8~m4CVmW1C{uwFMRZjChL ze~H*>)3;Xa~67o6=6^`yS_SN;^0*v4(qWQ`&^OUojKd-w z_tVa4-GCw+5e_8iW>7bJy0O!ZobKD25e_A^berE#J2{~n20c*K1Jrf|Jv-I2($<7F zgy*@ze1Y&HLC;HHCafpCLU@(%8bJ?BHI14!JtRGZpa-Le5}FfQ5SkD!VTdmy%qCn; zxI)*WD~Ze@Tt%2mxQ5^e*An!xP8Y=+2)gL$B6kzvX2Pw6+XxE@cMuj4?j+nrxSMbf zVKL!eLPIKVL^zPJFF}u2^*FV5bTo^I$QF9AmGF1MCj>oL{RiP+gl&Yc33{&j6yZrW zlr@B>3C|FoBfLO(nQ#FkFq3d0;UdBrgrS6CuI=Y(^@4Z%C)1v(glU9Q!e0pI5;%{& zazZX)1Ysm0k1&QXj!-}-BupX{5&97N5>6m=C+Gp{J%oD+_Y;;79w00w99x}OY}7g)oS4D&aIjPeK>MafBQ~S3)T)ysOrf2Lcl8jpFX042KSF=Pqxh_HxPVYjIFFD;$R>0qbRir^$RV6TwZjOyHPo%(;jp&V-RE1=PEQ%* zdjCCbYGM|x=tnq_&<&UF1brYmk)Ydu-R|r5UbpkQeb=3N9N#YTC-fk^RgK#c!aM(8 zYwrPGMe+V`Cx^1<08*0>!U00>F$oZQuTnzqEun*yfCy3(0i`HNkqk`)ML8E0XLpkjiGF|Y^>TSGe7QkGy$wVdU;vxi~Jl;9J_#NzgWLGc?iy(s+IiUWYeK!}GbzCcn$$i?9Q>z;o~vJP8I? zz%-Z+GeKL!@1c?R;RDdBN~@^GFrFkPz(lwohQcry4kJL5{t4Eco|*+ zt>L%Biy9Jl@(>N}$yEo4hmJ4EQAkAQZZwZl9bl9Kr|0qz6sEo(1Xd&dvJ6wqezG?)(BB-W;I3Ooo~$<*_( z4Yq@Jg|!R3jOVE^4W`2km{0uRCv7zWA1`7;8v&#Tp?R+CzYX}#42x>t+BMG(i%!jCM}WbK?h{wp(C_}R+^I9@Zg6MPzoYJuRbZD zH<51$xRaS<6|9CQKpVU3K}URk#_FP=jonZP18wL|!G8v5A6I*{!(jxBgak;0E`^HP zo3+}UZ426aZ4NC>;xBZL3OrVY>JS0H689Cj3fflHHtL`77aW7*@C|5}Q@fg9!wvFs z6K=uZ@E7dCeH-k6GG0``EO&sduEdE9F z(!j%ch=AhY2W<@Qpy6JF2N}j^!5nx9KE!Q59E8tdCIs&IVcrjuKpTeBpe?kA4iFF8 zADqBAbA)>R2tI&iR!;^Za+3;rE7dFZ0??bM-ZY;_Xco+dHSh$?hXwEm^oBmr7y7{f zNQ6P=&C9uqddhmuq06~TCVxg$dLhz#rQRd;-l$i?duad>bYQ)6H>b@tgKMB&p`YO= zco+7)7Y>tNZupTnPe41K<3W39yGg4OW(BAS`3h29&*3-=o`wyu z9Ol6c(9dHoKtp*MKuW-yu#k3C9`|z;=mPu#zri*56N=$?nK0eq1MDw@_RPvt6EiUH zhkIctjL6rEdP9w0K?03*5Y(VkYELW*DnLnCOovWV_xi~I$p$A+*!UqF*u0>2WAsq6-Pq>|g%2e=HQdkWSLL_xp z8p?pScFI9{$bnx@u%Hx0iiFZC8oUG*$n{8_$c&=G2BV8;>f<~*z5si$$DzAM&}!ng1pBsapb2{})~)Mbw$&=Kwd-SE)ujpO80 z8)8RbI*DrE@B(}TC*Tm|Kvo-{xiJ4C%y^hh;&b3}+_eq(H*!Ikb1+h%v)&H0t*33Y zk~k!jkvUL@+&+a?N}&av$Pa}!a1ZE2y*;#m-|)WY`O&cQ`E0!Kkd%sNuu4m)8a zY=p(IB4DTfb(uof{5}@A z?FF4na^@Ix9|h8xPG7i;%723^a20+x-(1h#JlUdYbw+Vrpcv0OMbrV|a2Nzt;0Lm! zt;_zH>o9c)s6#(DTy=E#9ZJ{E^|x>oj=}fn^;>u!3X#31sgNyT@vOtW1FD2iC5%Hj z6az0F(U>vN0yg1461Nec^SM>98fL=-paVA@u#JT{5~>Y#AQ$%B;02v>1VbCR2ee(; z1R4ZW35^gg3#DNbHCvnt7)8Y-p}^6gldbN!>oltq)JLuX=!9w!nR*PCz*2Y|bReYz zs4yr3Il%%i_#lR`v7o!I#ncHWkdvQDR0m3@p)P)EF1tXc(81Da&|%VH=zv>C_>(HR z0UK!rIzV~?jj1Y1(n6L}MNfKUNE#Rh z!$Di%8`M5%10x`k=hBc5biOl^w$coSg7)2Kl7U{}wg*+g%V_8o_={(>m;4X)4V4O^ z85Adnl`uPC>Ja8h0=eo-;(0u{)iH@@X;Rvgn@pWrY0usRO`$C`hZc^#BIyUtS}G%4 z1*(BcsZzKJxe=<)?r-_P7S(Hw^Ex*pt`()sZ6v%E84BEj+-*5#3k1fRC+h_H5`A* zMZw*mv|ePkc?Ob4=nvcnQ=lIxLM0+CC{g!W_DVD?C9GosMWi|oBg67jCjY`u@_KeN zv6DDog3Ta*ogRD+u?6_8h7WYBhkqORzj5ZiJGl@3J)tga!dZ*1ZnE>-1+z1>hbB-5 zYQcA;bqY>`s{A~%wLn9GhJ>0>70LnLK?Dix3XtU>B~E_{LZ3kt#s2wn+HT-JFy+#( zNd$!hwHdfINp0%q3xmjiP+^d}+$fuRbPzQjuouA$f#RU>VZeq!;NoX7$F7M-TN7oW zG?db2R3r~&pggEeR)$JY5h{SHRMlAxG>$|=6sT%r%#ub{5p%MkRW4Z?_?v$kg{6tp z@ExeVPK1r1=~bHT3df)y#KQ(S3ajBT>;=WK8{P!fjKV(y3NQIqAbF*mDorlu;c?Jt zJOReSy)X(!z%p12ir_I=1oL1n%z@c39VUY^ENv;C@sI>r;~S3KFz5|k)w8?s&3-b-w3`$%P%Ko}z--@YfRNHwLo`Ws$8l=Gs z5U(O`9 zybbTbKB@R4*bg7ThwvGE0iVN{@D(Vq{J#ci%C#Ti`4pUlZ{Y--gR^i3eu5w22gm#w zQ*)ixI?`J5-~71+mp~={1unt`xCuAlFSrc9LMUjwxQ=-heuFTOdvUk|zr!{71O5bM zN^;>)613RTrUDDEvm#X(WkO-336)-Qtw4Utw6x`Fss;9zs(+PqC%h;DP-f`n|AJl;d<*ps5p567QB5UU4 zcrm6j;>xHLvbT4_$gaq?38X@m;9eL3 z!=VohfL@>ybps6~T`{{rXXpuPPd&hmV<6A{p&#@G#Y1?02$I0PZA&0RMXrby&=CsQ zhhiTDi7*5P!!XB`d*)0ju~9G*l$bK3LMdKxKTL$Ns(-x}D%WG+KG5sIco+u-AqmEU zn}G*-R%HLbn5v7c5h-#tK9%ZmP(3fhoQA1(qQ<92r}m*1{V=9l{6m->Fg1!Q{8EU7 z#jp?*)(uyOXN65(z{7la1RjM&@E9xsjZ%t08C(geU|_lPtlpqCfHJJAlK%>jHZ{Ag z!+a7}!zy?J*1%d=58X&V=qVn)L^!J!RBE@h&m*JKhQcP;2rBUw%x7UUJO^9h8Bpn! zafOi<7dmmh#Pbf2|2F8L`d7bI|91n)L(eaRB60hgl1pCLJI$a0N;A@baGUJxE5zop@ z1llhcRKgE-WL%vw-=*MrRC_FY7%QqRR*+WH)%4Qb3>TpYsw736|1 z!Cj1e!E;&UzQt6go2dTX9IK&3f^ysxQ#p3Wd$sBla1_3QBcOJngzJD*T_01KJB+Ce zD!#JN4UU7tDxPC-KXMwql*O8by*U|Y- z3DEwS_Q})@PlMw48S^|`f*;{0_yv;B^5=VyfINNxbt84BGay0P<>y9nj^~ST!Eu+L zrZI36mU%$W2H&DuCxz%>0;MxC^dh55mj`%AgX? z%d;}?!_-z`ZqSiLPRIcs2!_9r`2&6hH&a)5Rt7Gs{#8QdOo=MTw?J;!;3nLFzd)Jz z6XbWpAnV?8mMRIvq^L;-5Jz zX(k6+WM;?A&uaN)Ky|H_o!PG}c4aEFn=+$n(ynl3cQwMTxXWLSPmR}ZbJV{YT?HJ~ zkfa3(Np%&mXO6@TTnTrzW@$rP(9+c15Cy6hHBwcB+D~OnwT0@K)u1X=0mZBMF01}! zQDjPVI87mQqAH~Xv%1CMu6|t$YV%wl@**cUZHg_z+y#vtKdrJlLOj&NO=HWmki3aM z3iKGf1v(g3K)u!K)w+QrESYi85WC!!pdwWy@>5mmkWba6!Kw{tuxbmfK~<%8p|@uZ zS{krgd8mKYt46A(n2M++W(#Nz%^Wjx>7@-tuKjA6igXiXmT|QfC^a(5zzf8!9bz{V zN~?*dD=NWJGgBu_Id;WVgNp=Z%oemKm+IL2KriSHJ&;l2lF{7J8+$KPBE%|{ENc>0 z)yim0jKJl{K|E_8q90~e%)Xe3kN^WfQ%8SHbsRAeT)WIsFdT+}YVn8@ui~6fA}Y!- zDRw9VLJlJ_N5CgMt4#OutkvB(OwCziF~`7tFagFx2js$F8qZVVZJr;%oD3@SV9Y77 zhwyHEa+CZ>4;@R6$uJXUfIJ5iPyq%(A|yZx?gKC_cmV&2umR@dJ_|H2&cmDy@_!m! zw}CwOhq?GY3=hE^=&O#Ul1flDB|%rnb(6ao0g8Ae$j{BhA|hG{kHRD1CMthD%g=Qi z<%Cnl73WAdBANHYz2HWCl7QdB37C!hgYY@eAHn9o!?%b9okAC3VYdC>id*%ofRu!Jr zO$|YAszEh*H;v4u8c!z)x{e>>;6AGg^{iHwHFDXrtF8g~$zQ5g;_6Dufcl%_lg9dD z_6Er%yK4VDOG2*uof3EZ&(}^EC8mT9J9Z_gjHnQDSHhB2_sJX|g{YTaVz=b3=?bd; zUv?sP{q$K#CsV$-RU_0Lx^?wS*KAXBvO#CY6t~LesY58;ILI4pRu#A6g14B@id$Wa zX)xD?F%9UtJa!I#hHj>z-zpWl4Cl>QbpX5&G>#82|}uIZ}_X#Nbs?H0y%SVEY;36q3*1@kZX6aIkT;1Z~hpTQi8`3vSnsLbWrO>hQ)R4M2Md)wc^_uxJ!m?h<{;$-Rh@>bb^l$*RH z&Vm|ZfAe#Bt95C&m|B#msK4Q*7B0J8;n;;9N&?cSMFRFFTj#H<^anN!aE{EsLJ@1cL%1sY!w?esSbD_UhkGpCZCck^yV zt41zc_$H5qGD`*0F-R)2+*po7-ms$%Ny>vyLQQBe%9r@;+f>tmn5V3GRsNFs#)ny znxGMw##O9bcPJGZE^0BmWo1F>jDaGLqGnl&wWY}%OW)w>;Rn?PvaowTcCIGKFebMbej~?rg8` zBK0z-^!3eTD5xcbU>1QwkRN+EGWv82^!aWtH&Ocdu2!U3F!Cb8uJv`c_uo%)0ivMA?Aw z>U>&7bi<6pUwfoAFk>MGv}#ec)#ABkuAm%RE4^3Kst{b&T&PLwsB7-3#dxeXpMc0< z%&^=XoAbQPw5)|hIWrU^R7)dW4AUjTp3oW=o0YY!+IMLoB3j}!z?#pRS%(!c>kbnfY;6*L9v>G;B_UM-Q(u`a!PLFf2`~w&W3ORulCV!POp%ul*WZrHWBKLM*98M#Y#4Y)G0yY5F! zVqL39=$*ah*R_gwkk?q${FD=;^yubTDcu&ElbUim9~4%)b4z=~{7PzpYLc_-y@luX z;6^3CtoH2uwj(R~=bWTobe@lxk@Z;W$yv$Bd`?x?(_Cs`RSOhY!EUR0Q@y@bm748c z->O*1&74~sN6eD?RuO!ksc$`2BD1fux*L?mfM!hR23Dzn$R5XeC8mO;=hiH2U@arE z(haR50m=7}cpvt|hww4H&X5~vIybb+me44fi%xfnNcKSp{aH7V^^sN^v!tPQcW_^` zx1seUV?bgfZ1K8*HZJ(I32sb(Ut&r(#_=oDxv}*alBXJ5Jww%bH1Furcr0;7LzL;z z#Ohg0GgeWYr8ezu72`P^%9}TvSY_*KrI3JES)6q1UzZVO*FAaNm)GroeFdQ9xt=vX z=vQ0gOtGfcM|V{~R6lgu2%^ldO|9CMm6sM+n?qx03AI46HgVkAV192pH?zk1qHs>Y zTHJit%xWGy*xc3JDxND3*}7_~^O&p)GZ8QvAw7@9T%W^^PlK04lW?044?+_5d69TBSR*qaHQwDUt~=1E#|?PyqeR z9I_j!L6(s$;#|OoFw`& zW>?Vf8tUC$Wj?_3C!m>0Zu()vfjnzQY=d1tOxPUz5lqP+#=M&neueoZ9D=VQ;MdAR zf~HZ#7clcQ=xbhTWtAYRptMh0SyOk~QB_cJVVxpp{aeRn5Wlu!;+;0p_IEhazfoo`&)HFC~ zRmrm$2^wk27LQ3fXoZKUgDb?PcV;B5`TFB8Ji%8yW(_{+l5gZNyRpaVD>oKTDLp%X z*`fdC4^I&{X)4t<_|UFW)^1sPE-du(hw!O`Pb`JVY4UwWSgjhkiIsA5bny11GA)iG zQHvZSp{0Pza_#!G5pVZ;{25PhQ(TJTG9s5r8exU|=VDj7LwfIfdu@Srx!7XBh4zxN zJeOId*mq!8`Z0NaJy)ewNKQ}iT3lERr2L7Q7c>6ux1TDue_sgc*0l3nE0@_d)(ZEK zN@O=Hv@XR^pU;DEpWrpeTK(~4QXrFP-fnD)=KPR8-M?wDr-f5M!hDw7L}o-29;aVV zQC0?xFW0b1-U4-yR0R-+&uhp2S+6Yz@452i51!zeF{q#>Z^pSy0}ARPOS`?kK$Zdp zze9fF?6{ix%+{OUaL;?W&2EKsRORWJ$6Wf*3e82v2IVow(TzE{-U_t>ZR6)Wre<%e zy?+-aQ5N(3+_OaEb&p;ri#2SWEX-#{ZzbCA@|nf=Sh1dS`OM5M-f(mL9xEd52K6T$ z^f+~~=JTzhmV1I@q8^BPfQCDO+EEo2FO%cR&g*JYG8#qC+h1c*p}Yia!o*0ftIFk zDO9gF!aUtK4|->8NN|-vKVnoaWNx&jwbVnxkHqOYlOHbrV7uQkBzhDw(d}q0(~wZ> zS-YhB^Ml76>6Ia&FQSU!6Z1>V$JO@kn3du4Y#}pKVc&7W4*qG%CwbPb&XoQUe998` zgOz1Uzw_Ivz8PU}6fz$Z*6%M&E|EA?Bjwb>J+0?tNYpNz+MZhMP}oGYN9`jDn|SO| z)mhf^U&^Ht8%tbVnESL-Q8BDMP~QoJ4ahWLy)J(WUCzK-PIjv?GM>PQ{dbfPJ^_Mv6_AN>3- zqKQ>AiwSCqk20TihM3JAN$8ajb6obfL(K0TtyP|) zp=RCN*lUHFt)0+lJk_X}hR*zX@`T=}HYp9eFatu(NhIP%AR(PMj8B?U;-R-21k#G3 zb;iubhnL)xdNJ*~MLhWJR(v#?q1^SMCaN>Vb2^h*(%GsV_afqIc#GEW=zep?r`-{E z29j5B*3h5FXN>P#KHOSYHN3%(3BDP1Vm_n~H|%wIyy*}a(8 zLXE#GW{xn^NoJuiGY8XO$%%RFpLMTw7}ceCAk8?%JlB)D2|dJxnVHGxp(U#g4P23v z+HRi^(smDm(NT3Z>f57RxWA(#(f!D&s)b9nn;MW{U}69ZGh@4v*fAv3_FrxC1}ewff_w_Z9w-UK&k9Nslu;?hWP^b@$$H zUCg!S#Dz+MIAc`P+0P{{i0rg>+d&5We+W3^7p_VS;AajVpTO=dswCY zq12$pq`NmY+VkT>gJuMZ6&UO`mNbidSSg-PrOfr*-u51*zl5Gd)3=nFZfTt9X~lc4 zMw%NviL*i(6V?kItSe*Bg)1I!^3Ip9_6jGuT2VEesvOeGs!}e7F0R@0eN4@oiEo#F zxAlr*(pF6*DLZj}+{=y=v%QDGeNAudWy+gs&oCYz=xs$fEh4lOt?gKOJH|PUzbxMN z;0LXVNfQP!T`h0A^dW|Xie{b6(v{4`KD6l=-c{A$=3154c6fS+TD>Ncx-7Zyk-qDf zYBON!(zfjaK7po~R@pS@OYu=dQeSezoW7thJ?e`prb54fd!RvmTg4o_5AT$!W>G&H z*21cGQs=J?`etAeZ_Pk41DPvc&8&SKN!s|OM2dB#pH(=|wQ6>6%ay|v?2kV0Fo8KU z4O$t{p9JnOT~ZR2-sd$-``h|EiKOSnP;;F)HPuB8AY>Wzrh%;GzQKb(srcK3481kU zZKfmPaZO(hb5Wy4@G}J1(ti8BYSa>0HaiCD6sGo3#n#YP~Zey;W`X(fZxP+d4HsFQU_b zt`OI2ncRt1Y+RE#y9ISmIRE$c#fiTM^3N)cbs#=Uwc&4jUff@>)(CuR+auG>I5QSy zdpg!O^N7Rat&=*3amCHHQx~q?zmH@~5s~Nk!_!yHuQApWTq`Or(CM$#HH`)lQE)xe zYYS0$;_d8W)G5aKOsTcQo>Jde(T4#o-5@@P*FGVQFF{U zU`^zY)p}&|VLg+7FsXR!n@WSpZb*I854%6HzFqXWxt3NsR{z*B;?fG2xJK1Ci;;-a z;$Hna(|XHM!DskrakzC{%+Apl2d~Y@2MyzdV!uP9(+o%K3?YSwkPn8CRzoQD9Tug| zGMc!y4I!@OEFaa)yMJ-M+|+Redk1nFh|6ifTC)YpNQkhZ#GP2*w3T_PshK&HlJsXS zq8RoTu77o4`=uWdgN9W!JGr^NP+7Uv)c)+*MJ)q90WB{;v69BAz-tM6 za;?pmifg&O8aI(6tit7bI1wIj%zDGN+_$XafKg5`YU5~YlW!yue~*Nkd((l9TjzXk z^)DF`=v3?AxI5H>UN0!#9hUc6Arm|6@ebBcBU)+dg7P+ie5@1)6UYoIzH0VGpTFO=ZZ{dijOlh)N5z9lC-CJJJV@B zX2*8s#;wbne|+`5+#9pX*&qnN$o&Gvf< znH+DYwf3pPlU{gpWLUxAA-HJ0 zE%x*Arqfi+tEBOy6`nfAsu^;pbWEEuR=w;7j#}vhhdRisV^BPu|NI!L`wr@5aj7b! zx_J`|54l5i*EVOyT9N)%oouZ?b#7|aiT!??i>9<@BQ;kubiYR9NG-T?Y6?UAf^n3z zW{8QD4mx)>d&XhXb+>=b5};bDH^i%k6dhE!i^-pqNu+RBvv`C39u?Kq^pjW1uIA)q z%uaYEp?lNSUYu|FIpw}O-`>5?6I{i46PeP@oFIh1FcNwbS+TLog+G2Ab}mE0&8-us zDY(KaZU&CGin+DozvJu>SdB&ZFxxmw^VjcTFGzpu(X_$7$qAj&MQq)uINm4S*gN(x zH^$R?diOAGC!iCj#{Gi`srI(D{e@ngi+=QaM#wQe%%Vw@(bk@4R*%$$*p~E2Wli9J z!U=Y;Wc-|^r7j-K2$rSE#9gOC)T7Uhd1rO6pE_&I(^{FSb2D+sXGE=m8}F`mVL3jv z?8>(*Db{1JKw|wZQJylnc0ym9z@*QZ8M zuD<4^y0Kkpp0K{Apjxd{MgB5~1J(?R>TA|5qRUL0Y(@C%BBuCL z3#K%!(SI9r6TK`dhJkNIUvu#Sx_uf6)#Uf*Uys-mdGj+OiYD`PW4EKYt->Ad&}7sD zXwcPE77ck0v^Luxpt-u?&h<0-H_-0t^|w)rG^`P z&_MI*)J**>`)=Si_K5?{%#~z-#x_7Wt}7)P@&V z?{;ggMv$w_e`|LBy-x4~r@pO0K3kZ=Gq?bVokV zh#xYXW}SkY83GmGy9WKQI-gtnZ>l#gt~l?iC0dX$lY0I zkCVawNYnO?`*<*i7nFBd#pJ~97vrx5%FFf(TkH5}W*@MnK$(k?leLpsn_UySWWFJ0 z^n$K ziw|2xbJQ+7%;cM`!m>lIWmbXat&SCFc%e?ZXk4RVrimmvAfZKZ#I-ROo^4lVLApe3 zn$GrztgxIhwfhY-i)LFbykni%Os9Lj5$4Kls`G{6rXYEXTi2W8CZhSV)6^Uty06qq zkM^fL?H_Irl1)RN8yWFJk7@WgZ|+&j<=H#Jbec;!zZ+pL9U^NtN7z$Wz1JS;_E+eT z9d4Ypf+9wmlcf4D#d9)msyt-Hd2CB~x@ng^EN87C*T>09_7T=f*G8tNFYh%8BfO!x z=z-_%O}$nob?GX} zNafdYCUPF>=14N}$1qDLnd#$reP*{}>^!S|-1#JXZmh9%@YKCetYb6jTU^x0u3@qj zkaDhmV&aFf@xDMQYev4=a!xdRe#QJh*iS6@clrN| zzyHoT)ZF*K<_I~hTGOZ6K&uGLStn@R6f@*ewnO^S0}D&VXI8E1diTEeD8cDWcibQg zyo0+7+UzGRXM%E<6ukA;T*!<6oGJEPepmUo8npQA?x7^6nTghJZ^efH%R8Af+xyo~ zwKF`f-HG|n57VDxrrjr=c;VU=lWhES8g%XS=zo}LjxVOW@VZuT3Fg{l(?aILs;0>k%-V~WST+57Q|uBi>#-@g z%`?HfoKqlbiH)3-OUR|Y7u=ryk$UE~=APMVwk>7o`tP=KahAEYR7Jo21=zG*M)1F9 znPJPwVCrns{(XwLVYZ#O+1rPHTBUBs*BlM<&c1E7Ie?`9yE*pQv3lkHi1A}y%^Q$l zpO))4J&a#lr|!DZtsOVv^uFQlvAQkAT^`PS*qqNCfxR4yO<#by%g3w3q|6C3Wwv)}6EidnsvecV&Yd>;Z?>l7HM_#*qX?1Zlu)GDmjkrO4`X|kJzJBnI|t_pHQ#SF4JVR zRnBe5=D;cr*`l3y@|}N{Ji0h#?W-iCMIjye9D%h@+PPv>*_T)KEta7JXJxF(B(OYd z#Jc0A-FKX=(o(ddY_>|Nzv}IbpN&v|y>@ngJrv(+ z)49h^>6FQt``x_*wn|5>rwg)+yOt0hHb!5S{jDYDyzK5vzS{z_)?50JnrXa<@VIZk zUp|%bj_Ch^8BhIjb)LzueK_phPcs?|b7G^XN$lVfb7BM8KE1?#YaN_4vs2={HF{@q z-f#a}Vjh*GTea@!;O?=^N?>?#_BS$LK*_t*9tZPZe)7@2)raeCm$NX91=-hvQ?%G1 zdt)Q~PAjPxduQeuqSr~y1CLC6v&i}D3p4cW6v@N9d~E}IwGS)X$7QdKZ1#W1>i=mK zaN`-PMV`MO5B%g>%Dm;K$vdbmb-7t2^ZDgw=0>K{cbD6PX=sm6PFE}Y&H(Zqc*%$j z9JX$xD$XFGg?hJRQ!7>V*BRysK8#C2T}a{JVy_AO7h>@ zO0S$9t4)(MinEslw204pY*Y3)o%W#p>+8goEl#~=}bM0katN}pJ~N*mw`OWU4{Phot%I6L|9>4=Um zWW>61jqy`K{_RLe?GJ9)SNLeog}OQ8r2GCF(+P>VBS@&qF6ApaYQ*Q)b!I3D*5_yN zDTGh2t^F>S9^K{3fDe@x9h9Z|&B1L{Op7PXp6wK!#v1toCiR-6R&+qJW)%p=_!8loP``Pzv%`W2fw|mm=B4-n~p)&{@m_P8>eqTQXLc>j~8QRbYxC2>u6G;r+M<(FJI zl2S-S++QT9(Q3WT)AfJ83rJUz)nxKr^TNzl*02XIY~8$FS!r zAnS1WU+zv5Fxy{ZrgVj~YSrog?wD%Y@604jik()lg+=|jZNcqoZhToyJHEi!>z2!o z#T}M2Mn5-b)^a(cW?VK+m&}B2vg_90>cL584tsQ6Ld}dOKx1k^vrq31whm&`?attu zu8yo}yXtU8t@JU;&8zJmT9>3!d3{v_dwSHQ`={LMTY^l)+ShLW>&2F3&t`iS;rV)e zifI~KHBbN+K72m3*%W-0>FDj=c7Hjuc|n0ghjJ%*f?s{s?ptnFvSvN2Jv+JV+?|z! zzvOfFM%}0pt*=c!Szkw+j2BX_JG_>0CiCoKn)RHSxrZ5}?d!LwSwACIf z=T-0py|A{d7O<*QRFYjX&b0UM^KxLp;7novA7-zGTTP^v9?ndX-Z$NL;;s$Ok$X*_ zK?gg%bF=m7(@ySLx@VVrHwHIv-Jds&-ei!?eyVo6?n}>`N0Ib|ZZfx0Sxvn2yxAlB zvFFXvH@Zqm#45lNl~4Ig|Vc=5A@(BcN=W#7l` z_lEYKWiOlKdVkpVvibcTbmDB-qF$bMjr*{NEtSZbeG_S4_xzY}NjhU&>I^fD_9L+~(av31kssSvn%73xH*~U#1lJfR z?B}&`>MpzgxZ9KAZEGO{=7sIQx$d z`tD8B4@v)@NEAm`_1`SDeo>A1#z^paf+RwAo5hN&(r$B7ab>-Bf+U++>=p&p*lkXH zMCy%qo5CMc+4t-=2_F+8>m5--=(-s7Fle`V97+E#yY1QGr4}VCZvH&^XrMtcU@=wg zHa8SU*|+SHP7AMpedOG3!$N{{<5GegS9{Aue?n7p?WZ@JnV*nf*WCD)S@8+Ea_r{& zPq@zN3V*jJ^#HP0_n3$ST!rR#{h9;FvT1ctX3Abu@>BdB(|;)5UQE|%yZ>VB>WBw& zYOo2sBe!|mbota8*2%Ze9wOdOjz2Z-+c)$g?hL$lJbMUq*D?1F895<|@3a0I_WGpV zPd*nYeLz2KG8O#X+Ue}GhU8Ws`)-dp`8j#x&Qi!1+)w7B>h>?lqi>&S|0QOVIP?7% z_}g;f{x)yhn(O{>V*XLDyfoX1ovFYMkrFtoR7m&AYo-g~oxUO_=j$f-48c7doRMf| z9%UVH_A6dc*wTnRM4j8`4E{S_O5jL1aA1dEY?cGx6pwSPYZiRX2;^+Mrk_wcr=Q0A zHRz$^GW&a-CXLn~Ij&oh<+`N98FAW@NQ|5cSIVq^eR#KRm_dQ4X zjs%(xoY5)qE8pAcRQdrk~notBm~1Jrg%bT{nn*zcJGlgTM&$&p0zxV5YLVoiVGLv*h3Hx~}u-q2~NA z6u`L`lTF;Nw9{19UCLBP_RWaUT)eCxcInbT+eXIN>4abK>p#M0Dz;st#Fa(SnG1V+ zhCCUUZt&2kneCVmQ_H>S;*{77_>Igs8Kd!q=d9~q0W$B&ix_F}(K%z>juLgnc5QOa z3W?Mr;?8$7{;!?IX#=HBZ`*K}>Slo_#v zm#iC<{Sco;p;*y=bt|S0Tb0ysgFncTG!ba*={0nxD!DzTzz|@{T}xM)TQJ48{eo4@ zEWF00Yb=|uF}-J5SbFxqX{!9m0_A^Q92PZIuUj2G9eborzi!P5W~rFwxnX&NS$U=9 zzR4=tGiY~OrN6E7!Je{jrG*81>v`Orb2BQ3ca6VuEqgT+(|AMq>9Y$(FkuC*Bgg9N zp)xu7jE(O%l5%oGDw`ub!aKJ?%&wf?2=_XYxs=m;(39y>7oUS1&*hEv*f*t9JnY;o z&FvkQ`D-~o6ALx{EpG|uletytkwtrnIl5I^d#|@sus&v7>mz5RdtPRfxU_nCy!~?W z$w}IZ{N4!#x!szUuavir_g{97?4y-f)RJ1+8)}}d=rx|)Z<%J5$Rpko*KK;zN{jZmVL_aw{xcF;%Nh-ypIN|;(WAsYyrZV z=w_rtu*FSC2m6*L=pvJQr?%c$^FUL+f6MBa(i~mbj_qC3>~D4lMYjBF%~n4ZM-#XRfL(XZM<{Rr7v~y)-;Ry@aM;S z{*jZ6f(6n>Ho}x_M}x|gwRHo$?M+g9@7c_bQd@cw1>?Q5GJ6st#kA>oyJO}MX(^q& zb?#XH+w`b9OWV~Yi)bMBE!}R9-Oc2Pw6GrD(YHmdSj@qm-dQ0vcw5sUX+&uGLJyYh z`f)+t#nKyKN-r81#opD+TgB_XW|?)pyb=Ehr+2tOxZCASkKW#z*$bz?yYWJ|r?NC{S2ov4I8*ke5`$uJ^m;?R2YchvLIcGB40~$Frkk&xQ$d^FJ zaL+&MC3x5A1?;#S?a#wPP2NQBLwpUHwlvYZHkcblX|aR7`85cbX2ZQZ<0`hYN47)x z>y9lP+U_1kHr@{P#`n)p4QA?FgV^Z*;ZuX#?^2q_MtEQIJl)r{9Z6A~Po(+amDjv* zGi;PM!rzGA=*#0v-k824;@J>2_U6mMmDDt@q;AdokNcw}!BUt+SNz+5W1Y?@0n**N5if zou4nJFD~`&^oO!_qVw7woj;0CEVxtKjlB3#3qg@3%f6}Oe(l{x^xmr^E~NYoJZfDJ^n1Iyl$BfWYThP$Sl;$L~o&B_Rpc{XHn92Z1KK| zI-K82$z-n6@29xGoAUWq@_r+qxww^B?2qEZO}*z)^H}GHQ3m8s+wiW zw*&L&Z>U-Q4T+o=7>!r-P2;n%QNaumX?=e2vblm<=3OCAg$tw|y5b$1z7kFw@P~J| z2WM0AI$HZ>b6S_{-gzDmp9>$q;azp7UlP)NNzxjT%RXc%QRYr=9mH)#-D>`T2xvd-vgzPm+J#IgO~IFf~wzrAO&`-GKuQ2X9oIG;i< zU+ZJvnH*^x`p;hd#%ANj; zM0~`nA4^KRiqE4 zHPgL_uc=v4#24+kUft|3;%g)`Pl&H%ner+2H2uMX9XIxM>b$WQc71Djdg82+1&7>o ztZ|BIE4jBhsR+T{Q}v;NshjehKZIQuQVJYdG54?3kKX*@4YL3_&kMuM7RTHl;=5IB z_Um?h_q4qF?8V<-KUXP?+a3Hnk#;K7SHfeS{>rz+H)iD6kz*1k4jy4@9`fDq9X;Xx z>Vp%pEg77*qNOdiH`NP!@dUQmma>bB07|d zR=g6ce&56?qlfYff4&l?Nj+aK^H>?*bW`A-y#8GJcDjc7rX9L$l<0fhd|C$Y=%&6< z)1;>FwKU?|6Kt*?_Epb2c*3~*?yG)ZQsU&Xrb!=P6VvmEuUy*uF}@YSChGS*-n7!; zzIHxyD%uxf9x3e`UuBgu8vd5YN*h_hH!!bBsNoAU57zK)OuJsw_o!tyRq_=#HxBv2()Kj) z&AuzGV_V|4}w3H)x4lVkBi#eHy diff --git a/client/package.json b/client/package.json index d3c5d012d..df34c5a26 100644 --- a/client/package.json +++ b/client/package.json @@ -10,8 +10,8 @@ "preview-prod": "cross-env NODE_ENV=development dotenv -e ../.env -- vite preview", "test": "cross-env NODE_ENV=test jest --watch", "test:ci": "cross-env NODE_ENV=test jest --ci", - "b:build": "NODE_ENV=production bunx --bun vite build", - "b:dev": "NODE_ENV=development bunx --bun vite" + "b:build": "NODE_ENV=production bun vite build", + "b:dev": "NODE_ENV=development bun vite" }, "repository": { "type": "git", diff --git a/client/src/components/Auth/__tests__/Login.spec.tsx b/client/src/components/Auth/__tests__/Login.spec.tsx index 6b8b66f1e..7025779f8 100644 --- a/client/src/components/Auth/__tests__/Login.spec.tsx +++ b/client/src/components/Auth/__tests__/Login.spec.tsx @@ -18,6 +18,15 @@ const setup = ({ data: {}, isSuccess: false, }, + useRefreshTokenMutationReturnValue = { + isLoading: false, + isError: false, + mutate: jest.fn(), + data: { + token: 'mock-token', + user: {}, + }, + }, useGetStartupCongfigReturnValue = { isLoading: false, isError: false, @@ -47,12 +56,17 @@ const setup = ({ .spyOn(mockDataProvider, 'useGetStartupConfig') //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult .mockReturnValue(useGetStartupCongfigReturnValue); + const mockUseRefreshTokenMutation = jest + .spyOn(mockDataProvider, 'useRefreshTokenMutation') + //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult + .mockReturnValue(useRefreshTokenMutationReturnValue); const renderResult = render(); return { ...renderResult, mockUseLoginUser, mockUseGetUserQuery, mockUseGetStartupConfig, + mockUseRefreshTokenMutation, }; }; diff --git a/client/src/components/Auth/__tests__/Registration.spec.tsx b/client/src/components/Auth/__tests__/Registration.spec.tsx index 0c40b29c0..9c55548d1 100644 --- a/client/src/components/Auth/__tests__/Registration.spec.tsx +++ b/client/src/components/Auth/__tests__/Registration.spec.tsx @@ -19,6 +19,15 @@ const setup = ({ isSuccess: false, error: null as Error | null, }, + useRefreshTokenMutationReturnValue = { + isLoading: false, + isError: false, + mutate: jest.fn(), + data: { + token: 'mock-token', + user: {}, + }, + }, useGetStartupCongfigReturnValue = { isLoading: false, isError: false, @@ -48,7 +57,10 @@ const setup = ({ .spyOn(mockDataProvider, 'useGetStartupConfig') //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult .mockReturnValue(useGetStartupCongfigReturnValue); - + const mockUseRefreshTokenMutation = jest + .spyOn(mockDataProvider, 'useRefreshTokenMutation') + //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult + .mockReturnValue(useRefreshTokenMutationReturnValue); const renderResult = render(); return { @@ -56,6 +68,7 @@ const setup = ({ mockUseRegisterUserMutation, mockUseGetUserQuery, mockUseGetStartupConfig, + mockUseRefreshTokenMutation, }; }; diff --git a/client/src/components/Nav/Logout.tsx b/client/src/components/Nav/Logout.tsx index 7d97e5a00..455a4ba14 100644 --- a/client/src/components/Nav/Logout.tsx +++ b/client/src/components/Nav/Logout.tsx @@ -9,7 +9,6 @@ const Logout = forwardRef(() => { const handleLogout = () => { logout(); - window.location.reload(); }; return ( diff --git a/client/src/components/Plugins/Store/__tests__/PluginStoreDialog.spec.tsx b/client/src/components/Plugins/Store/__tests__/PluginStoreDialog.spec.tsx index fd8b87043..fb49590f3 100644 --- a/client/src/components/Plugins/Store/__tests__/PluginStoreDialog.spec.tsx +++ b/client/src/components/Plugins/Store/__tests__/PluginStoreDialog.spec.tsx @@ -113,6 +113,15 @@ const setup = ({ plugins: ['wolfram'], }, }, + useRefreshTokenMutationReturnValue = { + isLoading: false, + isError: false, + mutate: jest.fn(), + data: { + token: 'mock-token', + user: {}, + }, + }, useAvailablePluginsQueryReturnValue = { isLoading: false, isError: false, @@ -137,6 +146,10 @@ const setup = ({ .spyOn(mockDataProvider, 'useGetUserQuery') //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult .mockReturnValue(useGetUserQueryReturnValue); + const mockUseRefreshTokenMutation = jest + .spyOn(mockDataProvider, 'useRefreshTokenMutation') + //@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult + .mockReturnValue(useRefreshTokenMutationReturnValue); const mockSetIsOpen = jest.fn(); const renderResult = render(); @@ -145,6 +158,7 @@ const setup = ({ mockUseGetUserQuery, mockUseAvailablePluginsQuery, mockUseUpdateUserPluginsMutation, + mockUseRefreshTokenMutation, mockSetIsOpen, }; }; diff --git a/client/src/hooks/AuthContext.tsx b/client/src/hooks/AuthContext.tsx index a120cfd6b..3c6c8aff8 100644 --- a/client/src/hooks/AuthContext.tsx +++ b/client/src/hooks/AuthContext.tsx @@ -107,12 +107,7 @@ const AuthContextProvider = ({ }); }; - const logout = () => { - document.cookie.split(';').forEach((c) => { - document.cookie = c - .replace(/^ +/, '') - .replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); - }); + const logout = useCallback(() => { logoutUser.mutate(undefined, { onSuccess: () => { setUserContext({ @@ -126,7 +121,25 @@ const AuthContextProvider = ({ doSetError((error as Error).message); }, }); - }; + }, [setUserContext, logoutUser]); + + const silentRefresh = useCallback(() => { + refreshToken.mutate(undefined, { + onSuccess: (data: TLoginResponse) => { + const { user, token } = data; + if (token) { + setUserContext({ token, isAuthenticated: true, user }); + } else { + console.log('Token is not present. User is not authenticated.'); + navigate('/login'); + } + }, + onError: (error) => { + console.log('refreshToken mutation error:', error); + navigate('/login'); + }, + }); + }, []); useEffect(() => { if (userQuery.data) { @@ -139,12 +152,7 @@ const AuthContextProvider = ({ doSetError(undefined); } if (!token || !isAuthenticated) { - const tokenFromCookie = getCookieValue('token'); - if (tokenFromCookie) { - setUserContext({ token: tokenFromCookie, isAuthenticated: true, user: userQuery.data }); - } else { - navigate('/login', { replace: true }); - } + silentRefresh(); } }, [ token, @@ -157,23 +165,23 @@ const AuthContextProvider = ({ setUserContext, ]); - // const silentRefresh = useCallback(() => { - // refreshToken.mutate(undefined, { - // onSuccess: (data: TLoginResponse) => { - // const { user, token } = data; - // setUserContext({ token, isAuthenticated: true, user }); - // }, - // onError: error => { - // setError(error.message); - // } - // }); - // - // }, [setUserContext]); + useEffect(() => { + const handleTokenUpdate = (event) => { + console.log('tokenUpdated event received event'); + const newToken = event.detail; + setUserContext({ + token: newToken, + isAuthenticated: true, + user: user, + }); + }; - // useEffect(() => { - // if (token) - // silentRefresh(); - // }, [token, silentRefresh]); + window.addEventListener('tokenUpdated', handleTokenUpdate); + + return () => { + window.removeEventListener('tokenUpdated', handleTokenUpdate); + }; + }, [setUserContext, user]); // Make the provider update only when it should const memoedValue = useMemo( diff --git a/docs/general_info/breaking_changes.md b/docs/general_info/breaking_changes.md index dbb0b900c..76b3ee05c 100644 --- a/docs/general_info/breaking_changes.md +++ b/docs/general_info/breaking_changes.md @@ -5,7 +5,11 @@ Certain changes in the updates may impact cookies, leading to unexpected behaviors if not cleared properly. ## v0.5.8 -**If you have issues after updating, please try to clear your browser cache and cookies!** + +- It's now required to set a JWT_REFRESH_SECRET in your .env file as of [#927](https://github.com/danny-avila/LibreChat/pull/927) + - It's also recommended you set REFRESH_TOKEN_EXPIRY or the default value will be used. + +## v0.5.8 - It's now required to name manifest JSON files (for [ChatGPT Plugins](..\features\plugins\chatgpt_plugins_openapi.md)) in the `api\app\clients\tools\.well-known` directory after their `name_for_model` property should you add one yourself. - This was a recommended convention before, but is now required. diff --git a/e2e/.env.test.example b/e2e/.env.test.example deleted file mode 100644 index e7a3fc48e..000000000 --- a/e2e/.env.test.example +++ /dev/null @@ -1,9 +0,0 @@ -# Test database. You can use your actual MONGO_URI if you don't mind it potentially including test data. -MONGO_URI=mongodb://127.0.0.1:27017/chatgpt-jest - -# Credential encryption/decryption for testing -CREDS_KEY=c3301ad2f69681295e022fb135e92787afb6ecfeaa012a10f8bb4ddf6b669e6d -CREDS_IV=cd02538f4be2fa37aba9420b5924389f - -# For testing the ChatAgent -OPENAI_API_KEY=your-api-key diff --git a/e2e/playwright.config.local.ts b/e2e/playwright.config.local.ts index 4f27241f6..b6ceee5e1 100644 --- a/e2e/playwright.config.local.ts +++ b/e2e/playwright.config.local.ts @@ -13,8 +13,10 @@ const config: PlaywrightTestConfig = { ...mainConfig.webServer, command: `node ${absolutePath}`, env: { - NODE_ENV: 'production', ...process.env, + NODE_ENV: 'development', + SESSION_EXPIRY: '60000', + REFRESH_TOKEN_EXPIRY: '300000', }, }, fullyParallel: false, // if you are on Windows, keep this as `false`. On a Mac, `true` could make tests faster (maybe on some Windows too, just try) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index dc7124b62..7c6fe7107 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ /* Run tests in files in parallel. NOTE: This sometimes causes issues on Windows. Set to false if you experience issues running on a Windows machine. */ - fullyParallel: true, + fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ @@ -62,7 +62,8 @@ export default defineConfig({ env: { ...process.env, NODE_ENV: 'development', - SESSION_EXPIRY: '86400000', + SESSION_EXPIRY: '60000', + REFRESH_TOKEN_EXPIRY: '300000', }, }, }); diff --git a/e2e/setup/authenticate.ts b/e2e/setup/authenticate.ts index 3aba926b4..d0bb2f2ea 100644 --- a/e2e/setup/authenticate.ts +++ b/e2e/setup/authenticate.ts @@ -22,13 +22,8 @@ async function authenticate(config: FullConfig, user: User) { if (!baseURL) { throw new Error('🤖: baseURL is not defined'); } - await page.goto(baseURL); + await page.goto(baseURL, { timeout: 5000 }); await login(page, user); - // const loginPromise = page.getByTestId('landing-title').waitFor({ timeout: 25000 }); // due to GH Actions load time - // if (process.env.NODE_ENV === 'ci') { - // await page.screenshot({ path: 'login-screenshot.png' }); - // } - // await loginPromise; await page.waitForURL(`${baseURL}/chat/new`); console.log('🤖: ✔️ user successfully authenticated'); // Set localStorage before navigating to the page diff --git a/e2e/specs/keys.spec.ts b/e2e/specs/keys.spec.ts index 8e30ef5fa..021f260d6 100644 --- a/e2e/specs/keys.spec.ts +++ b/e2e/specs/keys.spec.ts @@ -13,7 +13,7 @@ const enterTestKey = async (page: Page, endpoint: string) => { test.describe('Key suite', () => { // npx playwright test --config=e2e/playwright.config.local.ts --headed e2e/specs/keys.spec.ts test('Test Setting and Revoking Keys', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); const endpoint = 'chatGPTBrowser'; const newTopicButton = page.getByTestId('new-conversation-menu'); @@ -50,7 +50,7 @@ test.describe('Key suite', () => { }); test('Test Setting and Revoking Keys from Settings', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); const endpoint = 'bingAI'; const newTopicButton = page.getByTestId('new-conversation-menu'); diff --git a/e2e/specs/landing.spec.ts b/e2e/specs/landing.spec.ts index 6eee1f997..86421cb6f 100644 --- a/e2e/specs/landing.spec.ts +++ b/e2e/specs/landing.spec.ts @@ -1,15 +1,14 @@ -/* eslint-disable no-undef */ import { expect, test } from '@playwright/test'; test.describe('Landing suite', () => { test('Landing title', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); const pageTitle = await page.textContent('#landing-title'); expect(pageTitle?.length).toBeGreaterThan(0); }); test('Create Conversation', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); async function getItems() { const navDiv = await page.waitForSelector('nav > div'); diff --git a/e2e/specs/messages.spec.ts b/e2e/specs/messages.spec.ts index a81ff9cd3..27f0087ec 100644 --- a/e2e/specs/messages.spec.ts +++ b/e2e/specs/messages.spec.ts @@ -19,7 +19,7 @@ const waitForServerStream = async (response: Response) => { }; async function clearConvos(page: Page) { - await page.goto(initialUrl); + await page.goto(initialUrl, { timeout: 5000 }); await page.getByRole('button', { name: 'test' }).click(); await page.getByText('Settings').click(); await page.getByTestId('clear-convos-initial').click(); @@ -47,7 +47,7 @@ test.afterAll(async () => { }); test.beforeEach(async ({ page }) => { - await page.goto(initialUrl); + await page.goto(initialUrl, { timeout: 5000 }); }); test.afterEach(async ({ page }) => { @@ -60,7 +60,7 @@ test.describe('Messaging suite', () => { }) => { test.setTimeout(120000); const message = 'hi'; - await page.goto(initialUrl); + await page.goto(initialUrl, { timeout: 5000 }); await page.locator('#new-conversation-menu').click(); await page.locator(`#${endpoint}`).click(); await page.locator('form').getByRole('textbox').click(); @@ -125,7 +125,7 @@ test.describe('Messaging suite', () => { test('message should stop and continue', async ({ page }) => { const message = 'write me a 10 stanza poem about space'; - await page.goto(initialUrl); + await page.goto(initialUrl, { timeout: 5000 }); await page.locator('#new-conversation-menu').click(); await page.locator(`#${endpoint}`).click(); @@ -161,7 +161,7 @@ test.describe('Messaging suite', () => { // in this spec as we are testing post-message navigation, we are not testing the message response test('Page navigations', async ({ page }) => { - await page.goto(initialUrl); + await page.goto(initialUrl, { timeout: 5000 }); await page.getByTestId('convo-icon').first().click({ timeout: 5000 }); const currentUrl = page.url(); const conversationId = currentUrl.split(basePath).pop() ?? ''; diff --git a/e2e/specs/nav.spec.ts b/e2e/specs/nav.spec.ts index ecd9269d1..d8f997058 100644 --- a/e2e/specs/nav.spec.ts +++ b/e2e/specs/nav.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; test.describe('Navigation suite', () => { test('Navigation bar', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').click(); const navBar = await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').isVisible(); @@ -10,7 +10,7 @@ test.describe('Navigation suite', () => { }); test('Settings modal', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').click(); await page.getByText('Settings').click(); diff --git a/e2e/specs/popup.spec.ts b/e2e/specs/popup.spec.ts index edf87ad28..7055507ed 100644 --- a/e2e/specs/popup.spec.ts +++ b/e2e/specs/popup.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; test.describe('Endpoints Presets suite', () => { test('Endpoints Suite', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); await page.getByTestId('new-conversation-menu').click(); // includes the icon + endpoint names in obj property diff --git a/e2e/specs/settings.spec.ts b/e2e/specs/settings.spec.ts index cc7cf72a6..41bf774b0 100644 --- a/e2e/specs/settings.spec.ts +++ b/e2e/specs/settings.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; test.describe('Settings suite', () => { test('Last Bing settings', async ({ page }) => { - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); await page.evaluate(() => window.localStorage.setItem( 'lastConversationSetup', @@ -23,7 +23,7 @@ test.describe('Settings suite', () => { }), ), ); - await page.goto('http://localhost:3080/'); + await page.goto('http://localhost:3080/', { timeout: 5000 }); const initialLocalStorage = await page.evaluate(() => window.localStorage); const lastConvoSetup = JSON.parse(initialLocalStorage.lastConversationSetup); diff --git a/package.json b/package.json index 14304f10a..bff7ba80e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "upgrade": "node config/upgrade.js", "create-user": "node config/create-user.js", "backend": "cross-env NODE_ENV=production node api/server/index.js", - "backend:dev": "cross-env NODE_ENV=development npx nodemon api/server/index.js", + "backend:dev": "cross-env NODE_ENV=production npx nodemon api/server/index.js", "backend:stop": "node config/stop-backend.js", "build:data-provider": "cd packages/data-provider && npm run build", "frontend": "npm run build:data-provider && cd client && npm run build", @@ -34,6 +34,8 @@ "e2e:ci": "playwright test --config=e2e/playwright.config.ts", "e2e:debug": "cross-env PWDEBUG=1 playwright test --config=e2e/playwright.config.local.ts", "e2e:codegen": "npx playwright codegen --load-storage=e2e/storageState.json http://localhost:3080/chat/new", + "e2e:login": "npx playwright codegen --save-storage=e2e/auth.json http://localhost:3080/login", + "e2e:github": "act -W .github/workflows/playwright.yml --secret-file my.secrets", "test:client": "cd client && npm run test", "test:api": "cd api && npm run test", "e2e:update": "playwright test --config=e2e/playwright.config.js --update-snapshots", diff --git a/packages/data-provider/src/request.ts b/packages/data-provider/src/request.ts index 07e93e76a..6d7c82ac4 100644 --- a/packages/data-provider/src/request.ts +++ b/packages/data-provider/src/request.ts @@ -1,4 +1,69 @@ -import axios, { AxiosRequestConfig } from 'axios'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import axios, { AxiosRequestConfig, AxiosError } from 'axios'; +// eslint-disable-next-line import/no-cycle +import { refreshToken } from './data-service'; +import { setTokenHeader } from './headers-helpers'; + +let isRefreshing = false; +let failedQueue: { resolve: (value?: any) => void; reject: (reason?: any) => void }[] = []; + +const processQueue = (error: AxiosError | null, token: string | null = null) => { + failedQueue.forEach((prom) => { + if (error) { + prom.reject(error); + } else { + prom.resolve(token); + } + }); + failedQueue = []; +}; + +axios.interceptors.response.use( + (response) => response, + (error) => { + const originalRequest = error.config; + if (error.response.status === 401 && !originalRequest._retry) { + if (isRefreshing) { + return new Promise(function (resolve, reject) { + failedQueue.push({ resolve, reject }); + }) + .then((token) => { + originalRequest.headers['Authorization'] = 'Bearer ' + token; + return axios(originalRequest); + }) + .catch((err) => { + return Promise.reject(err); + }); + } + + originalRequest._retry = true; + isRefreshing = true; + + return new Promise(function (resolve, reject) { + refreshToken() + .then(({ token }) => { + if (token) { + originalRequest.headers['Authorization'] = 'Bearer ' + token; + setTokenHeader(token); + window.dispatchEvent(new CustomEvent('tokenUpdated', { detail: token })); + processQueue(null, token); + resolve(axios(originalRequest)); + } else { + window.location.href = '/login'; + } + }) + .catch((err) => { + processQueue(err, null); + reject(err); + }) + .then(() => { + isRefreshing = false; + }); + }); + } + return Promise.reject(error); + }, +); async function _get(url: string, options?: AxiosRequestConfig): Promise { const response = await axios.get(url, { ...options }); diff --git a/packages/data-provider/src/sse.js b/packages/data-provider/src/sse.js index 7f350f1ff..58c204f57 100644 --- a/packages/data-provider/src/sse.js +++ b/packages/data-provider/src/sse.js @@ -4,6 +4,9 @@ * All rights reserved. */ +import { refreshToken } from './data-service'; +import { setTokenHeader } from './headers-helpers'; + var SSE = function (url, options) { if (!(this instanceof SSE)) { return new SSE(url, options); @@ -102,12 +105,27 @@ var SSE = function (url, options) { this.close(); }; - this._onStreamProgress = function (e) { + this._onStreamProgress = async function (e) { if (!this.xhr) { return; } - if (this.xhr.status !== 200) { + if (this.xhr.status === 401 && !this._retry) { + this._retry = true; + try { + const refreshResponse = await refreshToken(); + this.headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${refreshResponse.token}`, + }; + setTokenHeader(refreshResponse.token); + window.dispatchEvent(new CustomEvent('tokenUpdated', { detail: refreshResponse.token })); + this.stream(); + } catch (err) { + this._onStreamFailure(e); + return; + } + } else if (this.xhr.status !== 200) { this._onStreamFailure(e); return; }