Skip to content

Commit 98e952e

Browse files
committed
feat: add user authentication, actions linked to userId
1 parent b24083c commit 98e952e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+953
-150
lines changed

public/app/config/channels.js

+3
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,7 @@ module.exports = {
4242
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:show',
4343
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:respond',
4444
POST_ACTION_CHANNEL: 'action:post',
45+
SIGN_IN_CHANNEL: 'auth:signin',
46+
SIGN_OUT_CHANNEL: 'auth:signout',
47+
IS_AUTHENTICATED_CHANNEL: 'auth:authenticated:get',
4548
};

public/app/config/config.js

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const DEFAULT_DEVELOPER_MODE = false;
4646
const DEFAULT_GEOLOCATION_ENABLED = false;
4747
const DEFAULT_PROTOCOL = 'https';
4848
const DEFAULT_LOGGING_LEVEL = 'info';
49+
const DEFAULT_AUTHENTICATED = false;
4950

5051
module.exports = {
5152
DEFAULT_LOGGING_LEVEL,
@@ -62,4 +63,5 @@ module.exports = {
6263
ICON_PATH,
6364
PRODUCT_NAME,
6465
escapeEscapeCharacter,
66+
DEFAULT_AUTHENTICATED,
6567
};

public/app/config/messages.js

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const SUCCESS_CLEARING_USER_INPUT_MESSAGE =
5050
const CONNECTION_MESSAGE_HEADER = 'Connection Status';
5151
const CONNECTION_OFFLINE_MESSAGE = 'You are offline.';
5252
const CONNECTION_ONLINE_MESSAGE = 'You are online.';
53+
const ERROR_SIGNING_IN = 'There was an error logging in the user';
54+
const ERROR_SIGNING_OUT = 'There was an error logging out the user';
55+
const ERROR_GETTING_AUTHENTICATED = 'There was an error getting authenticated';
5356

5457
module.exports = {
5558
ERROR_GETTING_DEVELOPER_MODE,
@@ -89,4 +92,7 @@ module.exports = {
8992
CONNECTION_MESSAGE_HEADER,
9093
CONNECTION_OFFLINE_MESSAGE,
9194
CONNECTION_ONLINE_MESSAGE,
95+
ERROR_SIGNING_IN,
96+
ERROR_SIGNING_OUT,
97+
ERROR_GETTING_AUTHENTICATED,
9298
};

public/app/db.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const fsPromises = fs.promises;
1010

1111
const SPACES_COLLECTION = 'spaces';
1212
const ACTIONS_COLLECTION = 'actions';
13+
const USERS_COLLECTION = 'users';
1314

1415
// bootstrap database
1516
const ensureDatabaseExists = async (dbPath = DATABASE_PATH) => {
@@ -33,6 +34,7 @@ const bootstrapDatabase = (dbPath = DATABASE_PATH) => {
3334
// set some defaults (required if json file is empty)
3435
db.defaults({
3536
[SPACES_COLLECTION]: [],
37+
[USERS_COLLECTION]: [],
3638
user: { lang: DEFAULT_LANG },
3739
[ACTIONS_COLLECTION]: [],
3840
}).write();
@@ -41,6 +43,7 @@ const bootstrapDatabase = (dbPath = DATABASE_PATH) => {
4143

4244
module.exports = {
4345
SPACES_COLLECTION,
46+
USERS_COLLECTION,
4447
ensureDatabaseExists,
4548
bootstrapDatabase,
4649
};

public/app/listeners/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const setDeveloperMode = require('./setDeveloperMode');
1919
const clearUserInput = require('./clearUserInput');
2020
const showClearUserInputPrompt = require('./showClearUserInputPrompt');
2121
const postAction = require('./postAction');
22+
const signIn = require('./signIn');
23+
const signOut = require('./signOut');
24+
const isAuthenticated = require('./isAuthenticated');
2225

2326
module.exports = {
2427
loadSpace,
@@ -42,4 +45,7 @@ module.exports = {
4245
clearUserInput,
4346
showClearUserInputPrompt,
4447
postAction,
48+
signIn,
49+
signOut,
50+
isAuthenticated,
4551
};
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const { IS_AUTHENTICATED_CHANNEL } = require('../config/channels');
2+
const logger = require('../logger');
3+
const { ERROR_GENERAL } = require('../config/errors');
4+
const { DEFAULT_AUTHENTICATED } = require('../config/config');
5+
6+
const isAuthenticated = (mainWindow, db) => async () => {
7+
try {
8+
const authenticated =
9+
db.get('user.authenticated').value() || DEFAULT_AUTHENTICATED;
10+
mainWindow.webContents.send(IS_AUTHENTICATED_CHANNEL, authenticated);
11+
} catch (e) {
12+
logger.error(e);
13+
mainWindow.webContents.send(IS_AUTHENTICATED_CHANNEL, ERROR_GENERAL);
14+
}
15+
};
16+
17+
module.exports = isAuthenticated;

public/app/listeners/signIn.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const ObjectId = require('bson-objectid');
2+
const { USERS_COLLECTION } = require('../db');
3+
const { SIGN_IN_CHANNEL } = require('../config/channels');
4+
5+
const DEFAULT_USER = {};
6+
7+
const signIn = (mainWindow, db) => async (event, { username, password }) => {
8+
const users = db.get(USERS_COLLECTION);
9+
10+
// @TODO hash password and check password validity
11+
12+
// check in db if username exists
13+
const user = users.find({ username }).value();
14+
if (user) {
15+
mainWindow.webContents.send(SIGN_IN_CHANNEL, user);
16+
} else {
17+
const userId = ObjectId().str;
18+
// assignment inside function to avoid sharing the same array among users
19+
const newUser = { userId, username, password, ...DEFAULT_USER };
20+
users.push(newUser).write();
21+
mainWindow.webContents.send(SIGN_IN_CHANNEL, newUser);
22+
}
23+
};
24+
25+
module.exports = signIn;

public/app/listeners/signOut.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const { SIGN_OUT_CHANNEL } = require('../config/channels');
2+
const { ERROR_GENERAL } = require('../config/errors');
3+
const logger = require('../logger');
4+
5+
const signOut = mainWindow => async () => {
6+
try {
7+
// clear cookies
8+
// session.defaultSession.clearStorageData(
9+
// { options: { storages: ['cookies'] } },
10+
// () => {}
11+
// );
12+
13+
mainWindow.webContents.send(SIGN_OUT_CHANNEL);
14+
} catch (e) {
15+
logger.error(e);
16+
mainWindow.webContents.send(SIGN_OUT_CHANNEL, ERROR_GENERAL);
17+
}
18+
};
19+
20+
module.exports = signOut;

public/electron.js

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
ipcMain,
66
Menu,
77
dialog,
8+
session,
89
// eslint-disable-next-line import/no-extraneous-dependencies
910
} = require('electron');
1011
const path = require('path');
@@ -51,6 +52,9 @@ const {
5152
CLEAR_USER_INPUT_CHANNEL,
5253
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL,
5354
POST_ACTION_CHANNEL,
55+
SIGN_IN_CHANNEL,
56+
SIGN_OUT_CHANNEL,
57+
IS_AUTHENTICATED_CHANNEL,
5458
} = require('./app/config/channels');
5559
const env = require('./env.json');
5660
const {
@@ -75,6 +79,9 @@ const {
7579
clearUserInput,
7680
showClearUserInputPrompt,
7781
postAction,
82+
signIn,
83+
signOut,
84+
isAuthenticated,
7885
} = require('./app/listeners');
7986
const isMac = require('./app/utils/isMac');
8087

@@ -397,6 +404,14 @@ app.on('ready', async () => {
397404

398405
// called when creating an action
399406
ipcMain.on(POST_ACTION_CHANNEL, postAction(mainWindow, db));
407+
// called when logging in a user
408+
ipcMain.on(SIGN_IN_CHANNEL, signIn(mainWindow, db));
409+
410+
// called when logging out a user
411+
ipcMain.on(SIGN_OUT_CHANNEL, signOut(mainWindow, session));
412+
413+
// called when getting authenticated
414+
ipcMain.on(IS_AUTHENTICATED_CHANNEL, isAuthenticated(mainWindow, db));
400415

401416
// called when getting AppInstanceResources
402417
ipcMain.on(GET_APP_INSTANCE_RESOURCES_CHANNEL, (event, data = {}) => {

src/App.js

+28-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import SpaceScreen from './components/space/SpaceScreen';
1818
import DeveloperScreen from './components/developer/DeveloperScreen';
1919
import { OnlineTheme, OfflineTheme } from './themes';
2020
import Dashboard from './components/dashboard/Dashboard';
21+
import LoginScreen from './components/login/LoginScreen';
22+
import Authorization from './components/Authorization';
2123
import {
2224
SETTINGS_PATH,
2325
SPACE_PATH,
@@ -27,14 +29,15 @@ import {
2729
LOAD_SPACE_PATH,
2830
DEVELOPER_PATH,
2931
DASHBOARD_PATH,
32+
LOGIN_PATH,
3033
} from './config/paths';
3134
import {
3235
getGeolocation,
3336
getUserFolder,
3437
getLanguage,
3538
getDeveloperMode,
3639
getGeolocationEnabled,
37-
} from './actions/user';
40+
} from './actions';
3841
import { DEFAULT_LANGUAGE } from './config/constants';
3942
import {
4043
CONNECTION_MESSAGE_HEADER,
@@ -146,22 +149,40 @@ export class App extends Component {
146149
<Router>
147150
<div className="app" style={{ height }}>
148151
<Switch>
149-
<Route exact path={HOME_PATH} component={Home} />
152+
<Route exact path={LOGIN_PATH} component={LoginScreen} />
153+
<Route
154+
exact
155+
path={HOME_PATH}
156+
component={Authorization()(Home)}
157+
/>
150158
<Route
151159
exact
152160
path={SPACES_NEARBY_PATH}
153-
component={SpacesNearby}
161+
component={Authorization()(SpacesNearby)}
162+
/>
163+
<Route
164+
exact
165+
path={VISIT_PATH}
166+
component={Authorization()(VisitSpace)}
167+
/>
168+
<Route
169+
exact
170+
path={LOAD_SPACE_PATH}
171+
component={Authorization()(LoadSpace)}
154172
/>
155-
<Route exact path={VISIT_PATH} component={VisitSpace} />
156-
<Route exact path={LOAD_SPACE_PATH} component={LoadSpace} />
157173
<Route exact path={SETTINGS_PATH} component={Settings} />
158-
<Route exact path={SPACE_PATH} component={SpaceScreen} />
174+
<Route
175+
exact
176+
path={SPACE_PATH}
177+
component={Authorization()(SpaceScreen)}
178+
/>
179+
<Route exact path={DASHBOARD_PATH} component={Dashboard} />
159180
<Route
160181
exact
161182
path={DEVELOPER_PATH}
162183
component={DeveloperScreen}
163184
/>
164-
<Route exact path={DASHBOARD_PATH} component={Dashboard} />
185+
<Route exact path={LOGIN_PATH} component={LoginScreen} />
165186
</Switch>
166187
</div>
167188
</Router>

src/actions/authentication.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { toastr } from 'react-redux-toastr';
2+
import {
3+
FLAG_SIGNING_IN,
4+
FLAG_SIGNING_OUT,
5+
FLAG_GETTING_AUTHENTICATED,
6+
SIGN_IN_SUCCEEDED,
7+
SIGN_OUT_SUCCEEDED,
8+
IS_AUTHENTICATED_SUCCEEDED,
9+
} from '../types';
10+
import {
11+
ERROR_SIGNING_IN,
12+
ERROR_MESSAGE_HEADER,
13+
ERROR_SIGNING_OUT,
14+
ERROR_GETTING_AUTHENTICATED,
15+
} from '../config/messages';
16+
import {
17+
SIGN_IN_CHANNEL,
18+
SIGN_OUT_CHANNEL,
19+
IS_AUTHENTICATED_CHANNEL,
20+
} from '../config/channels';
21+
import { createFlag } from './common';
22+
import { ERROR_GENERAL } from '../config/errors';
23+
24+
const flagSigningInUser = createFlag(FLAG_SIGNING_IN);
25+
const flagSigningOutUser = createFlag(FLAG_SIGNING_OUT);
26+
const flagGettingAuthenticated = createFlag(FLAG_GETTING_AUTHENTICATED);
27+
28+
const signIn = async ({ username, password }) => async dispatch => {
29+
try {
30+
dispatch(flagSigningInUser(true));
31+
window.ipcRenderer.send(SIGN_IN_CHANNEL, { username, password });
32+
window.ipcRenderer.once(SIGN_IN_CHANNEL, async (event, user) => {
33+
if (user === ERROR_GENERAL) {
34+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SIGNING_IN);
35+
} else {
36+
// obtain user cookie
37+
// await fetch(REACT_APP_GRAASP_LOGIN, {
38+
// body: `email=${encodeURIComponent(
39+
// user.username
40+
// )}&password=${encodeURIComponent(user.password)}`,
41+
// headers: {
42+
// 'Content-Type': 'application/x-www-form-urlencoded',
43+
// },
44+
// method: 'post',
45+
// });
46+
47+
dispatch({
48+
type: SIGN_IN_SUCCEEDED,
49+
payload: user,
50+
});
51+
}
52+
dispatch(flagSigningInUser(false));
53+
});
54+
} catch (e) {
55+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SIGNING_IN);
56+
dispatch(flagSigningInUser(false));
57+
}
58+
};
59+
60+
const signOutUser = () => dispatch => {
61+
try {
62+
dispatch(flagSigningOutUser(true));
63+
window.ipcRenderer.send(SIGN_OUT_CHANNEL);
64+
window.ipcRenderer.once(SIGN_OUT_CHANNEL, (event, response) => {
65+
if (response === ERROR_GENERAL) {
66+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SIGNING_OUT);
67+
} else {
68+
dispatch({
69+
type: SIGN_OUT_SUCCEEDED,
70+
payload: response,
71+
});
72+
}
73+
dispatch(flagSigningOutUser(false));
74+
});
75+
} catch (e) {
76+
console.error(e);
77+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SIGNING_OUT);
78+
dispatch(flagSigningOutUser(false));
79+
}
80+
};
81+
82+
const isAuthenticated = async () => dispatch => {
83+
try {
84+
dispatch(flagGettingAuthenticated(true));
85+
window.ipcRenderer.send(IS_AUTHENTICATED_CHANNEL);
86+
window.ipcRenderer.once(
87+
IS_AUTHENTICATED_CHANNEL,
88+
(event, authenticated) => {
89+
if (authenticated === ERROR_GENERAL) {
90+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_GETTING_AUTHENTICATED);
91+
} else {
92+
dispatch({
93+
type: IS_AUTHENTICATED_SUCCEEDED,
94+
payload: authenticated,
95+
});
96+
}
97+
dispatch(flagGettingAuthenticated(false));
98+
}
99+
);
100+
} catch (e) {
101+
console.error(e);
102+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_GETTING_AUTHENTICATED);
103+
dispatch(flagGettingAuthenticated(false));
104+
}
105+
};
106+
107+
export { signIn, signOutUser, isAuthenticated };

src/actions/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './appInstance';
66
export * from './developer';
77
export * from './layout';
88
export * from './action';
9+
export * from './authentication';

0 commit comments

Comments
 (0)