UNPKG

5.64 kBJavaScriptView Raw
1/*
2 * OS.js - JavaScript Cloud/Web Desktop Platform
3 *
4 * Copyright (c) 2011-2020, Anders Evenrud <andersevenrud@gmail.com>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 * @author Anders Evenrud <andersevenrud@gmail.com>
28 * @licence Simplified BSD License
29 */
30
31const fs = require('fs-extra');
32const consola = require('consola');
33const logger = consola.withTag('Auth');
34const nullAdapter = require('./adapters/auth/null.js');
35
36/**
37 * Authentication Handler
38 */
39class Auth {
40
41 /**
42 * Creates a new instance
43 * @param {Core} core Core instance reference
44 * @param {Object} [options={}] Service Provider arguments
45 */
46 constructor(core, options = {}) {
47 const {requiredGroups, denyUsers} = core.configuration.auth;
48
49 this.core = core;
50 this.options = {
51 adapter: nullAdapter,
52 requiredGroups,
53 denyUsers,
54 ...options
55 };
56
57 try {
58 this.adapter = this.options.adapter(core, this.options.config);
59 } catch (e) {
60 this.core.logger.warn(e);
61 this.adapter = nullAdapter(core, this.options.config);
62 }
63 }
64
65 /**
66 * Destroys instance
67 */
68 destroy() {
69 if (this.adapter.destroy) {
70 this.adapter.destroy();
71 }
72 }
73
74 /**
75 * Initializes adapter
76 */
77 async init() {
78 if (this.adapter.init) {
79 return this.adapter.init();
80 }
81
82 return true;
83 }
84
85 /**
86 * Performs a login request
87 * @param {Object} req HTTP request
88 * @param {Object} res HTTP response
89 */
90 async login(req, res) {
91 const result = await this.adapter.login(req, res);
92
93 if (result) {
94 const profile = this.createUserProfile(req.body, result);
95 if (profile && this.checkLoginPermissions(profile)) {
96 await this.createHomeDirectory(profile, req, res);
97 req.session.user = profile;
98 req.session.save(() => res.status(200).json(profile));
99 return;
100 }
101 }
102
103 res.status(403)
104 .json({error: 'Invalid login or permission denied'});
105 }
106
107 /**
108 * Performs a logout request
109 * @param {Object} req HTTP request
110 * @param {Object} res HTTP response
111 */
112 async logout(req, res) {
113 await this.adapter.logout(req, res);
114
115 try {
116 req.session.destroy();
117 } catch (e) {
118 logger.warn(e);
119 }
120
121 res.json({});
122 }
123
124 /**
125 * Performs a register request
126 * @param {Object} req HTTP request
127 * @param {Object} res HTTP response
128 */
129 async register(req, res) {
130 if (this.adapter.register) {
131 const result = await this.adapter.register(req, res);
132
133 return res.json(result);
134 }
135
136 return res.status(403)
137 .json({error: 'Registration unavailable'});
138 }
139
140 /**
141 * Checks if login is allowed for this user
142 * @param {object} profile User profile
143 * @return {boolean}
144 */
145 checkLoginPermissions(profile) {
146 const {requiredGroups, denyUsers} = this.options;
147
148 if (denyUsers.indexOf(profile.username) !== -1) {
149 return false;
150 }
151
152 if (requiredGroups.length > 0) {
153 const passes = requiredGroups.every(name => {
154 return profile.groups.indexOf(name) !== -1;
155 });
156
157 return passes;
158 }
159
160 return true;
161 }
162
163 /**
164 * Creates user profile object
165 * @param {object} fields Input fields
166 * @param {object} result Login result
167 * @return {object}
168 */
169 createUserProfile(fields, result) {
170 const ignores = ['password'];
171 const required = ['username', 'id'];
172 const template = {
173 id: 0,
174 username: fields.username,
175 name: fields.username,
176 groups: this.core.config('auth.defaultGroups', [])
177 };
178
179 const missing = required
180 .filter(k => typeof result[k] === 'undefined');
181
182 if (missing.length) {
183 logger.warn('Missing user attributes', missing);
184 } else {
185 const values = Object.keys(result)
186 .filter(k => ignores.indexOf(k) === -1)
187 .reduce((o, k) => ({...o, [k]: result[k]}), {});
188
189 return {...template, ...values};
190 }
191
192 return false;
193 }
194
195 /**
196 * Tries to create home directory for a user
197 * @param {object} profile User profile
198 */
199 async createHomeDirectory(profile) {
200 try {
201 const homeDir = await this
202 .core
203 .make('osjs/vfs')
204 .realpath('home:/', profile);
205
206 await fs.ensureDir(homeDir);
207 } catch (e) {
208 console.warn('Failed trying to make home directory for', profile.username);
209 }
210 }
211}
212
213module.exports = Auth;