-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathAuth.js
241 lines (223 loc) · 9 KB
/
Auth.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import sha256 from 'crypto-js/sha256';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import ErrorHandler from '@/utils/ErrorHandler';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
import { isOidcEnabled } from '@/utils/OidcAuth';
/* Uses config accumulator to get and return app config */
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
return config.appConfig || {};
};
/**
* Called when the user is still using array for users, prints warning
* This was a breaking change, implemented in V 1.6.5
* Support for old user structure will be removed in V 1.7.0
*/
const printWarning = () => {
ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
};
/* Returns array of users from appConfig.auth, if available, else an empty array */
const getUsers = () => {
const appConfig = getAppConfig();
const auth = appConfig.auth || {};
// Check if the user is still using previous schema type
if (Array.isArray(auth)) {
printWarning(); // Print warning message
return []; // Support for old data structure now removed
}
// Otherwise, return the users array, if available
return auth.users || [];
};
/**
* Generates a 1-way hash, in order to be stored in local storage for authentication
* @param {String} user The username of user
* @returns {String} The hashed token
*/
const generateUserToken = (user) => {
if (!user.user || (!user.hash && !user.password)) {
ErrorHandler('Invalid user object. Must have `user` and either a `hash` or `password` param');
return undefined;
}
const passHash = user.hash || sha256(process.env[user.password]).toString().toUpperCase();
const strAndUpper = (input) => input.toString().toUpperCase();
const sha = sha256(strAndUpper(user.user) + strAndUpper(passHash));
return strAndUpper(sha);
};
export const getCookieToken = () => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${cookieKeys.AUTH_TOKEN}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
};
export const makeBasicAuthHeaders = () => {
const token = getCookieToken();
const bearerAuth = (token && token.length > 5) ? `Bearer ${token}` : null;
const username = process.env.VUE_APP_BASIC_AUTH_USERNAME
|| localStorage[localStorageKeys.USERNAME]
|| 'user';
const password = process.env.VUE_APP_BASIC_AUTH_PASSWORD || bearerAuth;
const basicAuth = `Basic ${btoa(`${username}:${password}`)}`;
const headers = password
? { headers: { Authorization: basicAuth, 'WWW-Authenticate': 'true' } }
: {};
return headers;
};
/**
* Checks if the user is currently authenticated
* @returns {Boolean} Will return true if the user is logged in, else false
*/
export const isLoggedIn = () => {
const users = getUsers();
const cookieToken = getCookieToken();
return users.some((user) => {
if (generateUserToken(user) === cookieToken) {
localStorage.setItem(localStorageKeys.USERNAME, user.user);
return true;
} else return false;
});
};
/* Returns true if authentication is enabled */
export const isAuthEnabled = () => {
const users = getUsers();
return (users.length > 0);
};
/* Returns true if guest access is enabled */
export const isGuestAccessEnabled = () => {
const appConfig = getAppConfig();
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled() && !isOidcEnabled()) {
return appConfig.auth.enableGuestAccess || false;
}
return false;
};
/**
* Checks credentials entered by the user against those in the config
* Returns an object containing a boolean indicating success/ failure
* along with a message outlining what's not right
* @param {String} username The username entered by the user
* @param {String} pass The password entered by the user
* @param {String[]} users An array of valid user objects
* @param {Object} messages A static message template object
* @returns {Object} An object containing a boolean result and a message
*/
export const checkCredentials = (username, pass, users, messages) => {
let response; // Will store an object containing boolean and message
if (!username) {
response = { correct: false, msg: messages.missingUsername };
} else if (!pass) {
response = { correct: false, msg: messages.missingPassword };
} else {
users.forEach((user) => {
if (user.user.toLowerCase() === username.toLowerCase()) { // User found
if (user.password) {
if (!user.password.startsWith('VUE_APP_')) {
ErrorHandler('Invalid password format. Please use VUE_APP_ prefix');
response = { correct: false, msg: messages.incorrectPassword };
} else if (!process.env[user.password]) {
ErrorHandler(`Missing environmental variable for ${user.password}`);
} else if (process.env[user.password] === pass) {
response = { correct: true, msg: messages.successMsg };
} else {
response = { correct: false, msg: messages.incorrectPassword };
}
} else if (user.hash && user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
response = { correct: true, msg: messages.successMsg }; // Password is correct
} else { // User found, but password is not a match
response = { correct: false, msg: messages.incorrectPassword };
}
}
});
}
return response || { correct: false, msg: messages.incorrectUsername };
};
/**
* Sets the cookie value in order to login the user locally
* @param {String} username - The users username
* @param {String} pass - Password, not yet hashed
* @param {Number} timeout - A desired timeout for the session, in ms
*/
export const login = (username, pass, timeout) => {
const now = new Date();
const expiry = new Date(now.setTime(now.getTime() + timeout)).toGMTString();
const userObject = { user: username, hash: sha256(pass).toString().toLowerCase() };
document.cookie = `${cookieKeys.AUTH_TOKEN}=${generateUserToken(userObject)};`
+ `${timeout > 0 ? `expires=${expiry}` : ''}`;
localStorage.setItem(localStorageKeys.USERNAME, username);
};
/**
* Removed the browsers' cookie, causing user to be logged out
*/
export const logout = () => {
document.cookie = `${cookieKeys.AUTH_TOKEN}=null`;
localStorage.removeItem(localStorageKeys.USERNAME);
};
/**
* If correctly logged in as a valid, authenticated user,
* then returns the user object for the current user
* If not logged in, will return false
* */
export const getCurrentUser = () => {
if (!isLoggedIn()) return false; // User not logged in
const username = localStorage[localStorageKeys.USERNAME]; // Get username
if (!username) return false; // No username
let foundUserObject = false; // Value to return
getUsers().forEach((user) => {
// If current logged-in user found, then return that user
if (user.user.toLowerCase() === username.toLowerCase()) foundUserObject = user;
});
return foundUserObject;
};
/**
* Checks if the user is viewing the dashboard as a guest
* Returns true if guest mode enabled, and user not logged in
* */
export const isLoggedInAsGuest = () => {
const guestEnabled = isGuestAccessEnabled();
const loggedIn = isLoggedIn();
return guestEnabled && !loggedIn;
};
/**
* Checks if the current user has admin privileges.
* If no users are set up, then function will always return true
* But if auth is configured, then will verify user is correctly
* logged in and then check weather they are of type admin, and
* return false if any conditions fail
* @returns {Boolean} - True if admin privileges
*/
export const isUserAdmin = () => {
const users = getUsers();
if (users.length === 0) return true; // Authentication not setup
if (!isLoggedIn()) return false; // Auth setup, but not signed in as a valid user
const currentUser = localStorage[localStorageKeys.USERNAME];
let isAdmin = false;
users.forEach((user) => {
if (user.user.toLowerCase() === currentUser.toLowerCase()) {
if (user.type === 'admin') isAdmin = true;
}
});
return isAdmin;
};
/**
* Determines which button should display, based on the user type
* 0 = Auth not configured (don't show anything)
* 1 = Auth configured, and user logged in (show logout button)
* 2 = Auth configured, guest access enabled, not logged in (show login)
* Note that if auth is enabled, but not guest access, and user not logged in,
* then they will never be able to view the homepage, so no button needed
*/
export const getUserState = () => {
const {
notConfigured,
loggedIn,
guestAccess,
keycloakEnabled,
oidcEnabled,
} = userStateEnum; // Numeric enum options
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
if (isOidcEnabled()) return oidcEnabled;
if (!isAuthEnabled()) return notConfigured; // No auth enabled
if (isLoggedIn()) return loggedIn; // User is logged in
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
return notConfigured;
};