diff --git a/app/scenes/ErrorSuspended/ErrorSuspended.js b/app/scenes/ErrorSuspended/ErrorSuspended.js index 009aa6b9..dd4ab86c 100644 --- a/app/scenes/ErrorSuspended/ErrorSuspended.js +++ b/app/scenes/ErrorSuspended/ErrorSuspended.js @@ -1,24 +1,29 @@ // @flow import React from 'react'; +import { inject, observer } from 'mobx-react'; import CenteredContent from 'components/CenteredContent'; import PageTitle from 'components/PageTitle'; +import AuthStore from 'stores/AuthStore'; -const ErrorSuspended = () => ( - - -

- - ⚠️ - {' '} - Your account has been suspended -

+const ErrorSuspended = observer(({ auth }: { auth: AuthStore }) => { + return ( + + +

+ + ⚠️ + {' '} + Your account has been suspended +

-

- A team admin has suspended your account. To re-activate your account, - please reach out to them directly. -

-
-); +

+ A team admin ({auth.suspendedContactEmail}) has + suspended your account. To re-activate your account, please reach out to + them directly. +

+
+ ); +}); -export default ErrorSuspended; +export default inject('auth')(ErrorSuspended); diff --git a/app/stores/AuthStore.js b/app/stores/AuthStore.js index 22a1f3a2..ef1c9936 100644 --- a/app/stores/AuthStore.js +++ b/app/stores/AuthStore.js @@ -15,6 +15,7 @@ class AuthStore { @observable oauthState: string; @observable isLoading: boolean = false; @observable isSuspended: boolean = false; + @observable suspendedContactEmail: ?string; /* Computed */ @@ -46,6 +47,7 @@ class AuthStore { } catch (err) { if (err.data.error === 'user_suspended') { this.isSuspended = true; + this.suspendedContactEmail = err.data.adminEmail; } } }; diff --git a/server/api/index.js b/server/api/index.js index 096a8c3e..fb41620b 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -29,6 +29,7 @@ api.use(async (ctx, next) => { } catch (err) { ctx.status = err.status || 500; let message = err.message || err.name; + let error; if (err instanceof Sequelize.ValidationError) { // super basic form error handling @@ -40,18 +41,21 @@ api.use(async (ctx, next) => { if (message.match('Authorization error')) { ctx.status = 403; + error = 'authorization_error'; } if (ctx.status === 500) { message = 'Internal Server Error'; + error = 'internal_server_error'; ctx.app.emit('error', err, ctx); } ctx.body = { ok: false, - error: _.snakeCase(err.id || err.message), + error: _.snakeCase(err.id || error), status: err.status, message, + adminEmail: err.adminEmail ? err.adminEmail : undefined, }; } }); diff --git a/server/api/middlewares/authentication.js b/server/api/middlewares/authentication.js index 85b865f2..26e06a28 100644 --- a/server/api/middlewares/authentication.js +++ b/server/api/middlewares/authentication.js @@ -76,7 +76,8 @@ export default function auth() { } if (user.isSuspended) { - throw new UserSuspendedError(); + const suspendingAdmin = await User.findById(user.suspendedById); + throw new UserSuspendedError({ adminEmail: suspendingAdmin.email }); } ctx.state.token = token; diff --git a/server/api/middlewares/authentication.test.js b/server/api/middlewares/authentication.test.js index da6606eb..a0a1208c 100644 --- a/server/api/middlewares/authentication.test.js +++ b/server/api/middlewares/authentication.test.js @@ -159,8 +159,10 @@ describe('Authentication middleware', async () => { it('should return an error for suspended users', async () => { const state = {}; + const admin = await buildUser({}); const user = await buildUser({ suspendedAt: new Date(), + suspendedById: admin.id, }); const authMiddleware = auth(); @@ -179,6 +181,7 @@ describe('Authentication middleware', async () => { expect(e.message).toEqual( 'Your access has been suspended by the team admin' ); + expect(e.adminEmail).toEqual(admin.email); } }); }); diff --git a/server/errors.js b/server/errors.js index bcad3828..0c509e99 100644 --- a/server/errors.js +++ b/server/errors.js @@ -19,11 +19,10 @@ export function AdminRequiredError( return httpErrors(403, message, { id: 'admin_required' }); } -export function UserSuspendedError( - message: string = 'Your access has been suspended by the team admin' -) { - return httpErrors(403, message, { +export function UserSuspendedError({ adminEmail }: { adminEmail: string }) { + return httpErrors(403, 'Your access has been suspended by the team admin', { id: 'user_suspended', + adminEmail, }); } diff --git a/server/models/User.js b/server/models/User.js index 44ed4a09..878757e4 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -29,6 +29,7 @@ const User = sequelize.define( slackData: DataTypes.JSONB, jwtSecret: encryptedFields.vault('jwtSecret'), suspendedAt: DataTypes.DATE, + suspendedById: DataTypes.UUID, }, { getterMethods: {