@@ -23,8 +26,8 @@ function SignupView() {
{t('SignupView.Or')}
-
-
+
+
);
}
-
-export default SignupView;
diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.ts
similarity index 71%
rename from client/modules/User/reducers.js
rename to client/modules/User/reducers.ts
index 2832190b28..584ef24adb 100644
--- a/client/modules/User/reducers.js
+++ b/client/modules/User/reducers.ts
@@ -1,6 +1,27 @@
+import type { CookieConsentOptions, PublicUser } from '../../../common/types';
import * as ActionTypes from '../../constants';
-const user = (state = { authenticated: false }, action) => {
+// User Action:
+export type UserAction = {
+ user?: PublicUser;
+ cookieConsent?: CookieConsentOptions;
+ type: any;
+};
+
+export const user = (
+ state: Partial & {
+ authenticated: boolean;
+ // TODO: use state of user from server as single source of truth:
+ // Currently using redux state below, but server also has similar info.
+ resetPasswordInitiate?: boolean;
+ resetPasswordInvalid?: boolean;
+ emailVerificationInitiate?: boolean;
+ emailVerificationTokenState?: 'checking' | 'verified' | 'invalid';
+ } = {
+ authenticated: false
+ },
+ action: UserAction
+) => {
switch (action.type) {
case ActionTypes.AUTH_USER:
return {
@@ -47,5 +68,3 @@ const user = (state = { authenticated: false }, action) => {
return state;
}
};
-
-export default user;
diff --git a/client/reducers.ts b/client/reducers.ts
index 2c65e555ca..99f1b63b57 100644
--- a/client/reducers.ts
+++ b/client/reducers.ts
@@ -4,7 +4,7 @@ import ide from './modules/IDE/reducers/ide';
import { preferences } from './modules/IDE/reducers/preferences';
import project from './modules/IDE/reducers/project';
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
-import user from './modules/User/reducers';
+import { user } from './modules/User/reducers';
import sketches from './modules/IDE/reducers/projects';
import toast from './modules/IDE/reducers/toast';
import console from './modules/IDE/reducers/console';
@@ -34,5 +34,8 @@ const rootReducer = combineReducers({
// Type for entire redux state
export type RootState = ReturnType;
+// Type for functions that get root state
+export type GetRootState = () => RootState;
+
// eslint-disable-next-line import/no-default-export
export default rootReducer;
diff --git a/client/routes.jsx b/client/routes.jsx
index 8926a95bdd..55d47453d6 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -6,17 +6,17 @@ import { Route as RouterRoute, Switch } from 'react-router-dom';
import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView';
-import About from './modules/About/pages/About';
-import CodeOfConduct from './modules/Legal/pages/CodeOfConduct';
-import PrivacyPolicy from './modules/Legal/pages/PrivacyPolicy';
-import TermsOfUse from './modules/Legal/pages/TermsOfUse';
-import LoginView from './modules/User/pages/LoginView';
-import SignupView from './modules/User/pages/SignupView';
-import ResetPasswordView from './modules/User/pages/ResetPasswordView';
-import EmailVerificationView from './modules/User/pages/EmailVerificationView';
-import NewPasswordView from './modules/User/pages/NewPasswordView';
-import AccountView from './modules/User/pages/AccountView';
-import CollectionView from './modules/User/pages/CollectionView';
+import { About } from './modules/About/pages/About';
+import { CodeOfConduct } from './modules/Legal/pages/CodeOfConduct';
+import { PrivacyPolicy } from './modules/Legal/pages/PrivacyPolicy';
+import { TermsOfUse } from './modules/Legal/pages/TermsOfUse';
+import { LoginView } from './modules/User/pages/LoginView';
+import { SignupView } from './modules/User/pages/SignupView';
+import { ResetPasswordView } from './modules/User/pages/ResetPasswordView';
+import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
+import { NewPasswordView } from './modules/User/pages/NewPasswordView';
+import { AccountView } from './modules/User/pages/AccountView';
+import { CollectionView } from './modules/User/pages/CollectionView';
import DashboardView from './modules/User/pages/DashboardView';
import { getUser } from './modules/User/actions';
import ProtectedSketchRoute from './protected-route';
diff --git a/client/testData/testReduxStore.ts b/client/testData/testReduxStore.ts
index 33223ae950..daf663ef92 100644
--- a/client/testData/testReduxStore.ts
+++ b/client/testData/testReduxStore.ts
@@ -54,7 +54,11 @@ const initialTestState: RootState = {
user: {
email: 'happydog@example.com',
username: 'happydog',
- preferences: {},
+ preferences: {
+ ...initialPrefState,
+ indentationAmount: 2,
+ isTabIndent: true
+ },
apiKeys: [],
verified: 'sent',
id: '123456789',
diff --git a/package-lock.json b/package-lock.json
index 9edffa05e9..1e1c123183 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -164,6 +164,7 @@
"@types/classnames": "^2.3.0",
"@types/friendly-words": "^1.2.2",
"@types/jest": "^29.5.14",
+ "@types/js-cookie": "^3.0.6",
"@types/mjml": "^4.7.4",
"@types/node": "^16.18.126",
"@types/nodemailer": "^7.0.1",
@@ -171,7 +172,10 @@
"@types/passport": "^1.0.17",
"@types/react": "^16.14.0",
"@types/react-dom": "^16.9.25",
+ "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-tabs": "^2.3.1",
+ "@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -16447,6 +16451,13 @@
"pretty-format": "^29.0.0"
}
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/js-levenshtein": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -16646,6 +16657,16 @@
"@types/react": "^16.0.0"
}
},
+ "node_modules/@types/react-helmet": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+ "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-redux": {
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
@@ -16680,6 +16701,26 @@
"@types/react-router": "*"
}
},
+ "node_modules/@types/react-tabs": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+ "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react/node_modules/csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
@@ -53149,6 +53190,12 @@
"pretty-format": "^29.0.0"
}
},
+ "@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true
+ },
"@types/js-levenshtein": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -53346,6 +53393,15 @@
"dev": true,
"requires": {}
},
+ "@types/react-helmet": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+ "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-redux": {
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
@@ -53378,6 +53434,22 @@
"@types/react-router": "*"
}
},
+ "@types/react-tabs": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+ "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "dev": true,
+ "requires": {}
+ },
"@types/redux-devtools-themes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz",
diff --git a/package.json b/package.json
index 643d468aa3..01d0ed4a7c 100644
--- a/package.json
+++ b/package.json
@@ -139,15 +139,18 @@
"@types/classnames": "^2.3.0",
"@types/friendly-words": "^1.2.2",
"@types/jest": "^29.5.14",
+ "@types/js-cookie": "^3.0.6",
"@types/mjml": "^4.7.4",
"@types/node": "^16.18.126",
"@types/nodemailer": "^7.0.1",
"@types/nodemailer-mailgun-transport": "^1.4.6",
"@types/passport": "^1.0.17",
- "@types/passport": "^1.0.17",
"@types/react": "^16.14.0",
"@types/react-dom": "^16.9.25",
+ "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-tabs": "^2.3.1",
+ "@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^5.62.0",
diff --git a/server/controllers/user.controller/__testUtils__.ts b/server/controllers/user.controller/__testUtils__.ts
index 9d851c49f8..7bc90bf0e6 100644
--- a/server/controllers/user.controller/__testUtils__.ts
+++ b/server/controllers/user.controller/__testUtils__.ts
@@ -29,7 +29,7 @@ export const mockBaseUserSanitised: PublicUser = {
email: 'test@example.com',
username: 'tester',
preferences: mockUserPreferences,
- apiKeys: ([] as unknown) as Types.DocumentArray,
+ apiKeys: [],
verified: 'verified',
id: 'abc123',
totalSize: 42,
@@ -42,6 +42,7 @@ export const mockBaseUserSanitised: PublicUser = {
export const mockBaseUserFull: Omit = {
...mockBaseUserSanitised,
name: 'test user',
+ apiKeys: ([] as unknown) as Types.DocumentArray,
tokens: [],
password: 'abweorij',
resetPasswordToken: '1i14ij23',
@@ -58,9 +59,15 @@ export const mockBaseUserFull: Omit = {
export function createMockUser(
overrides: Partial = {},
unSanitised: boolean = false
-): PublicUser & Record {
+): PublicUser | UserDocument {
+ if (unSanitised) {
+ return {
+ ...mockBaseUserFull,
+ ...overrides
+ } as UserDocument;
+ }
return {
- ...(unSanitised ? mockBaseUserFull : mockBaseUserSanitised),
+ ...mockBaseUserSanitised,
...overrides
- };
+ } as PublicUser;
}
diff --git a/server/controllers/user.controller/__tests__/apiKey.test.ts b/server/controllers/user.controller/__tests__/apiKey.test.ts
index 875b15b2b6..47a0b8fe8c 100644
--- a/server/controllers/user.controller/__tests__/apiKey.test.ts
+++ b/server/controllers/user.controller/__tests__/apiKey.test.ts
@@ -7,7 +7,11 @@ import { Types } from 'mongoose';
import { User } from '../../../models/user';
import { createApiKey, removeApiKey } from '../apiKey';
-import type { ApiKeyDocument, RemoveApiKeyRequestParams } from '../../../types';
+import type {
+ ApiKeyDocument,
+ RemoveApiKeyRequestParams,
+ UserDocument
+} from '../../../types';
import { createMockUser } from '../__testUtils__';
jest.mock('../../../models/user');
@@ -31,7 +35,7 @@ describe('user.controller > api key', () => {
describe('createApiKey', () => {
it("returns an error if user doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
User.findById = jest.fn().mockResolvedValue(null);
@@ -48,7 +52,7 @@ describe('user.controller > api key', () => {
});
it('returns an error if label not provided', async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
request.body = {};
const user = new User();
@@ -98,7 +102,7 @@ describe('user.controller > api key', () => {
describe('removeApiKey', () => {
it("returns an error if user doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
User.findById = jest.fn().mockResolvedValue(null);
@@ -115,7 +119,7 @@ describe('user.controller > api key', () => {
});
it("returns an error if specified key doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
request.params = { keyId: 'not-a-real-key' };
const user = new User();
user.apiKeys = ([] as unknown) as Types.DocumentArray;
@@ -145,11 +149,14 @@ describe('user.controller > api key', () => {
apiKeys.find = Array.prototype.find;
apiKeys.pull = jest.fn();
- const user = createMockUser({
- id: '1234',
- apiKeys,
- save: jest.fn()
- });
+ const user = createMockUser(
+ {
+ id: '1234',
+ apiKeys,
+ save: jest.fn()
+ },
+ true
+ ) as UserDocument;
request.user = user;
request.params = { keyId: 'id1' };
diff --git a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
index d2e68ee61a..2910b33569 100644
--- a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
@@ -5,6 +5,7 @@ import { Request, Response } from 'express';
import { unlinkGithub, unlinkGoogle } from '../../authManagement';
import { saveUser } from '../../helpers';
import { createMockUser } from '../../__testUtils__';
+import { UserDocument } from '../../../../types';
jest.mock('../../helpers', () => ({
...jest.requireActual('../../helpers'),
@@ -50,10 +51,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
});
});
describe('and when there is a user in the request', () => {
- const user = createMockUser({
- github: 'testuser',
- tokens: [{ kind: 'github' }, { kind: 'google' }]
- });
+ const user = createMockUser(
+ {
+ github: 'testuser',
+ tokens: [{ kind: 'github' }, { kind: 'google' }]
+ },
+ true
+ ) as UserDocument;
beforeEach(async () => {
request.user = user;
@@ -96,10 +100,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
});
});
describe('and when there is a user in the request', () => {
- const user = createMockUser({
- google: 'testuser',
- tokens: [{ kind: 'github' }, { kind: 'google' }]
- });
+ const user = createMockUser(
+ {
+ google: 'testuser',
+ tokens: [{ kind: 'github' }, { kind: 'google' }]
+ },
+ true
+ ) as UserDocument;
beforeEach(async () => {
request.user = user;
diff --git a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
index 248eeac4e0..b5336bffe4 100644
--- a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
@@ -29,7 +29,7 @@ describe('user.controller > auth management > password management', () => {
let response: MockResponse;
let next: MockNext;
let mockToken: string;
- let mockUser: Partial;
+ let mockUser: UserDocument;
const fixedTime = 100000000;
beforeEach(() => {
@@ -68,10 +68,13 @@ describe('user.controller > auth management > password management', () => {
describe('if the user is found', () => {
beforeEach(async () => {
mockToken = 'mock-token';
- mockUser = createMockUser({
- email: 'test@example.com',
- save: jest.fn().mockResolvedValue(null)
- });
+ mockUser = createMockUser(
+ {
+ email: 'test@example.com',
+ save: jest.fn().mockResolvedValue(null)
+ },
+ false
+ ) as UserDocument;
(generateToken as jest.Mock).mockResolvedValue(mockToken);
User.findByEmail = jest.fn().mockResolvedValue(mockUser);
@@ -143,10 +146,13 @@ describe('user.controller > auth management > password management', () => {
});
it('returns unsuccessful for all other errors', async () => {
mockToken = 'mock-token';
- mockUser = createMockUser({
- email: 'test@example.com',
- save: jest.fn().mockResolvedValue(null)
- });
+ mockUser = createMockUser(
+ {
+ email: 'test@example.com',
+ save: jest.fn().mockResolvedValue(null)
+ },
+ false
+ ) as UserDocument;
(generateToken as jest.Mock).mockRejectedValue(
new Error('network error')
@@ -298,7 +304,7 @@ describe('user.controller > auth management > password management', () => {
resetPasswordToken: 'valid-token',
resetPasswordExpires: fixedTime + 10000, // still valid
save: jest.fn()
- };
+ } as UserDocument;
beforeEach(async () => {
User.findOne = jest.fn().mockReturnValue({
diff --git a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
index 698dfa1aea..faf6a89297 100644
--- a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
@@ -60,13 +60,16 @@ describe('user.controller > auth management > updateSettings (email, username, p
response = new MockResponse();
next = jest.fn();
- startingUser = createMockUser({
- username: OLD_USERNAME,
- email: OLD_EMAIL,
- password: OLD_PASSWORD,
- id: '123459',
- comparePassword: jest.fn().mockResolvedValue(true)
- });
+ startingUser = createMockUser(
+ {
+ username: OLD_USERNAME,
+ email: OLD_EMAIL,
+ password: OLD_PASSWORD,
+ id: '123459',
+ comparePassword: jest.fn().mockResolvedValue(true)
+ },
+ false
+ ) as UserDocument;
testUser = { ...startingUser }; // copy to avoid mutation causing false-positive tests results
diff --git a/server/controllers/user.controller/__tests__/helpers.test.ts b/server/controllers/user.controller/__tests__/helpers.test.ts
index a6add4fda9..b04bb2548a 100644
--- a/server/controllers/user.controller/__tests__/helpers.test.ts
+++ b/server/controllers/user.controller/__tests__/helpers.test.ts
@@ -10,14 +10,17 @@ import { UserDocument } from '../../../types';
jest.mock('../../../models/user');
-const mockFullUser = createMockUser({
- // sensitive fields to be removed:
- name: 'bob dylan',
- tokens: [],
- password: 'password12314',
- resetPasswordToken: 'wijroaijwoer',
- banned: true
-});
+const mockFullUser = createMockUser(
+ {
+ // sensitive fields to be removed:
+ name: 'bob dylan',
+ tokens: [],
+ password: 'password12314',
+ resetPasswordToken: 'wijroaijwoer',
+ banned: true
+ },
+ true
+) as UserDocument;
const {
name,
diff --git a/server/controllers/user.controller/apiKey.ts b/server/controllers/user.controller/apiKey.ts
index e87747a75c..5a8cc613f0 100644
--- a/server/controllers/user.controller/apiKey.ts
+++ b/server/controllers/user.controller/apiKey.ts
@@ -67,7 +67,8 @@ export const createApiKey: RequestHandler<
await user.save();
const apiKeys = user.apiKeys.map((apiKey, index) => {
- const fields = apiKey.toObject!();
+ const fields = apiKey.toObject();
+ // only include the token of the most recently made apiKey to display in the copiable field
const shouldIncludeToken = index === addedApiKeyIndex - 1;
return shouldIncludeToken ? { ...fields, token: keyToBeHashed } : fields;
diff --git a/server/controllers/user.controller/helpers.ts b/server/controllers/user.controller/helpers.ts
index 36bcf86649..7d05826fb1 100644
--- a/server/controllers/user.controller/helpers.ts
+++ b/server/controllers/user.controller/helpers.ts
@@ -1,19 +1,33 @@
import crypto from 'crypto';
import type { Response } from 'express';
import { User } from '../../models/user';
-import { PublicUser, UserDocument } from '../../types';
+import {
+ ApiKeyDocument,
+ PublicUser,
+ SanitisedApiKey,
+ UserDocument
+} from '../../types';
+
+export function sanitiseApiKey(key: ApiKeyDocument): SanitisedApiKey {
+ return {
+ id: key.id,
+ label: key.label,
+ lastUsedAt: key.lastUsedAt,
+ createdAt: key.createdAt
+ };
+}
/**
* Sanitise user objects to remove sensitive fields
* @param user
* @returns Sanitised user
*/
-export function userResponse(user: PublicUser | UserDocument): PublicUser {
+export function userResponse(user: UserDocument): PublicUser {
return {
email: user.email,
username: user.username,
preferences: user.preferences,
- apiKeys: user.apiKeys,
+ apiKeys: user.apiKeys.map((el) => sanitiseApiKey(el)),
verified: user.verified,
id: user.id,
totalSize: user.totalSize,
diff --git a/server/types/apiKey.ts b/server/types/apiKey.ts
index 21ba15aa04..be21dfdbcd 100644
--- a/server/types/apiKey.ts
+++ b/server/types/apiKey.ts
@@ -8,14 +8,15 @@ export interface IApiKey extends VirtualId, MongooseTimestamps {
label: string;
lastUsedAt?: Date;
hashedKey: string;
+ token?: string;
}
/** Mongoose document object for API Key */
export interface ApiKeyDocument
extends IApiKey,
Omit, 'id'> {
- toJSON(options?: any): SanitisedApiKey;
- toObject(options?: any): SanitisedApiKey;
+ toJSON(options?: any): IApiKey;
+ toObject(options?: any): IApiKey;
}
/**
@@ -23,7 +24,10 @@ export interface ApiKeyDocument
* and can be exposed to the client
*/
export interface SanitisedApiKey
- extends Pick {}
+ extends Pick<
+ ApiKeyDocument,
+ 'id' | 'label' | 'lastUsedAt' | 'createdAt' | 'token'
+ > {}
/** Mongoose model for API Key */
export interface ApiKeyModel extends Model {}
diff --git a/server/types/user.ts b/server/types/user.ts
index 6d6d53fa1f..1f180fee37 100644
--- a/server/types/user.ts
+++ b/server/types/user.ts
@@ -2,7 +2,7 @@ import { Document, Model, Types } from 'mongoose';
import { VirtualId, MongooseTimestamps } from './mongoose';
import { UserPreferences, CookieConsentOptions } from './userPreferences';
import { EmailConfirmationStates } from './email';
-import { ApiKeyDocument } from './apiKey';
+import { ApiKeyDocument, SanitisedApiKey } from './apiKey';
import { Error, GenericResponseBody, RouteParam } from './express';
// -------- MONGOOSE --------
@@ -38,14 +38,17 @@ export interface PublicUser
| 'email'
| 'username'
| 'preferences'
- | 'apiKeys'
| 'verified'
| 'id'
| 'totalSize'
| 'github'
| 'google'
| 'cookieConsent'
- > {}
+ | 'totalSize'
+ > {
+ /** Can contain either raw ApiKeyDocuments (server side) or SanitisedApiKeys (client side) */
+ apiKeys: SanitisedApiKey[];
+}
/** Mongoose document object for User */
export interface UserDocument