aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJohannes Zellner <johannes@cloudron.io>2019-08-07 22:33:23 +0200
committerJohannes Zellner <johannes@cloudron.io>2019-08-07 22:33:23 +0200
commitc2c00fca7dccb6e512a0f01bc87db129538765ef (patch)
tree3b872bed1587231a22d24fe8aaf3043565900f3b
parentd5940df06a4f498176dad293f66607fb69eb2a28 (diff)
downloadSurfer-c2c00fca7dccb6e512a0f01bc87db129538765ef.tar.gz
Surfer-c2c00fca7dccb6e512a0f01bc87db129538765ef.tar.zst
Surfer-c2c00fca7dccb6e512a0f01bc87db129538765ef.zip
Add access token ui and rest api
-rw-r--r--frontend/css/style.css9
-rw-r--r--frontend/index.html16
-rw-r--r--frontend/js/app.js44
-rwxr-xr-xserver.js3
-rw-r--r--src/auth.js37
5 files changed, 105 insertions, 4 deletions
diff --git a/frontend/css/style.css b/frontend/css/style.css
index 901de34..b43d6fe 100644
--- a/frontend/css/style.css
+++ b/frontend/css/style.css
@@ -89,3 +89,12 @@ a:hover, a:focus {
89 align-items: center; 89 align-items: center;
90 justify-content: center; 90 justify-content: center;
91} 91}
92
93.access-token-input {
94 padding: 5px 0;
95 width: 450px;
96}
97
98.access-token-input > input, .access-token-input i {
99 cursor: copy !important;
100}
diff --git a/frontend/index.html b/frontend/index.html
index 2d97e1c..20154da 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -36,6 +36,21 @@
36 </span> 36 </span>
37 </el-dialog> 37 </el-dialog>
38 38
39 <el-dialog title="Access Tokens" :visible.sync="accessTokensDialogVisible" width="30%">
40 Tokens can be used with the surfer <a href="https://www.npmjs.com/package/cloudron-surfer" target="_blank">cli tool</a> or using the Api directly.
41 They are shared between all users.
42 <br/>
43 <br/>
44 <div>
45 <div v-for="accessToken in accessTokens">
46 <el-input suffix-icon="el-icon-copy-document" v-model="accessToken" class="access-token-input" @focus="onCopyAccessToken" size="small"></el-input>
47 <el-button icon="el-icon-delete" type="danger" size="small" @click="onDeleteAccessToken(accessToken)"></el-button>
48 </div>
49 </div>
50 <br/>
51 <el-button @click="onCreateAccessToken()" size="small" type="primary">Create Access Token</el-button>
52 </el-dialog>
53
39 <el-header> 54 <el-header>
40 <el-row type="flex" justify="space-between"> 55 <el-row type="flex" justify="space-between">
41 <div style="padding: 7px;"> 56 <div style="padding: 7px;">
@@ -66,6 +81,7 @@
66 </el-dropdown-item> 81 </el-dropdown-item>
67 <el-dropdown-item disabled divided>WebDAV Endpoint</el-dropdown-item> 82 <el-dropdown-item disabled divided>WebDAV Endpoint</el-dropdown-item>
68 <el-dropdown-item><a href="/_webdav/" target="_blank">{{ origin }}/_webdav/</a></el-dropdown-item> 83 <el-dropdown-item><a href="/_webdav/" target="_blank">{{ origin }}/_webdav/</a></el-dropdown-item>
84 <el-dropdown-item command="apiAccess" divided><i class="el-icon-connection"></i> Access Tokens</el-dropdown-item>
69 <el-dropdown-item command="about" divided><i class="el-icon-info"></i> About</el-dropdown-item> 85 <el-dropdown-item command="about" divided><i class="el-icon-info"></i> About</el-dropdown-item>
70 <el-dropdown-item command="logout" id="logoutButton"><i class="el-icon-circle-close"></i> Logout</el-dropdown-item> 86 <el-dropdown-item command="logout" id="logoutButton"><i class="el-icon-circle-close"></i> Logout</el-dropdown-item>
71 </el-dropdown-menu> 87 </el-dropdown-menu>
diff --git a/frontend/js/app.js b/frontend/js/app.js
index d99a840..05cbe9c 100644
--- a/frontend/js/app.js
+++ b/frontend/js/app.js
@@ -43,6 +43,8 @@ function initWithToken(accessToken) {
43 app.folderListingEnabled = !!result.body.folderListingEnabled; 43 app.folderListingEnabled = !!result.body.folderListingEnabled;
44 44
45 loadDirectory(decode(window.location.hash.slice(1))); 45 loadDirectory(decode(window.location.hash.slice(1)));
46
47 app.refreshAccessTokens();
46 }); 48 });
47 }); 49 });
48} 50}
@@ -278,7 +280,9 @@ var app = new Vue({
278 password: '', 280 password: '',
279 busy: false 281 busy: false
280 }, 282 },
281 entries: [] 283 entries: [],
284 accessTokens: [],
285 accessTokensDialogVisible: false
282 }, 286 },
283 methods: { 287 methods: {
284 onLogin: function () { 288 onLogin: function () {
@@ -312,6 +316,8 @@ var app = new Vue({
312 }).then(function () {}).catch(function () {}); 316 }).then(function () {}).catch(function () {});
313 } else if (command === 'logout') { 317 } else if (command === 'logout') {
314 logout(); 318 logout();
319 } else if (command === 'apiAccess') {
320 this.accessTokensDialogVisible = true;
315 } 321 }
316 }, 322 },
317 onDownload: function (entry) { 323 onDownload: function (entry) {
@@ -415,6 +421,42 @@ var app = new Vue({
415 }); 421 });
416 }).catch(function () {}); 422 }).catch(function () {});
417 }, 423 },
424 refreshAccessTokens: function () {
425 var that = this;
426
427 superagent.get('/api/tokens').query({ access_token: localStorage.accessToken }).end(function (error, result) {
428 if (error && !result) return that.$message.error(error.message);
429
430 that.accessTokens = result.body.accessTokens;
431 });
432 },
433 onCopyAccessToken: function (event) {
434 event.target.select();
435 document.execCommand('copy');
436
437 this.$message({ type: 'success', message: 'Access token copied to clipboard' });
438 },
439 onCreateAccessToken: function () {
440 var that = this;
441
442 superagent.post('/api/tokens').query({ access_token: localStorage.accessToken }).end(function (error, result) {
443 if (error && !result) return that.$message.error(error.message);
444
445 that.refreshAccessTokens();
446 });
447 },
448 onDeleteAccessToken: function (token) {
449 var that = this;
450
451 this.$confirm('All actions from apps using this token will fail!', 'Really delete this access token?', { confirmButtonText: 'Yes Delete', cancelButtonText: 'No' }).then(function () {
452 superagent.delete('/api/tokens/' + token).query({ access_token: localStorage.accessToken }).end(function (error, result) {
453 if (error && !result) return that.$message.error(error.message);
454
455 that.refreshAccessTokens();
456 });
457 }).catch(function () {});
458
459 },
418 prettyDate: function (row, column, cellValue, index) { 460 prettyDate: function (row, column, cellValue, index) {
419 var date = new Date(cellValue), 461 var date = new Date(cellValue),
420 diff = (((new Date()).getTime() - date.getTime()) / 1000), 462 diff = (((new Date()).getTime() - date.getTime()) / 1000),
diff --git a/server.js b/server.js
index d3e4c6c..4c9df2d 100755
--- a/server.js
+++ b/server.js
@@ -77,6 +77,9 @@ router.post ('/api/login', auth.login);
77router.post ('/api/logout', auth.verify, auth.logout); 77router.post ('/api/logout', auth.verify, auth.logout);
78router.get ('/api/settings', auth.verify, getSettings); 78router.get ('/api/settings', auth.verify, getSettings);
79router.put ('/api/settings', auth.verify, setSettings); 79router.put ('/api/settings', auth.verify, setSettings);
80router.get ('/api/tokens', auth.verify, auth.getTokens);
81router.post ('/api/tokens', auth.verify, auth.createToken);
82router.delete('/api/tokens/:token', auth.verify, auth.delToken);
80router.get ('/api/profile', auth.verify, auth.getProfile); 83router.get ('/api/profile', auth.verify, auth.getProfile);
81router.get ('/api/files/*', auth.verify, files.get); 84router.get ('/api/files/*', auth.verify, files.get);
82router.post ('/api/files/*', auth.verify, multipart, files.post); 85router.post ('/api/files/*', auth.verify, multipart, files.post);
diff --git a/src/auth.js b/src/auth.js
index 2532688..a885d49 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -15,6 +15,8 @@ const LDAP_USERS_BASE_DN = process.env.CLOUDRON_LDAP_USERS_BASE_DN;
15const LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json'); 15const LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json');
16const TOKENSTORE_FILE = path.resolve(process.env.TOKENSTORE_FILE || './.tokens.json'); 16const TOKENSTORE_FILE = path.resolve(process.env.TOKENSTORE_FILE || './.tokens.json');
17const AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local'; 17const AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local';
18const LOGIN_TOKEN_PREFIX = 'login-';
19const API_TOKEN_PREFIX = 'api-';
18 20
19if (AUTH_METHOD === 'ldap') { 21if (AUTH_METHOD === 'ldap') {
20 console.log('Use ldap auth'); 22 console.log('Use ldap auth');
@@ -34,8 +36,11 @@ var tokenStore = {
34 get: function (token, callback) { 36 get: function (token, callback) {
35 callback(tokenStore.data[token] ? null : 'not found', tokenStore.data[token]); 37 callback(tokenStore.data[token] ? null : 'not found', tokenStore.data[token]);
36 }, 38 },
37 set: function (token, data, callback) { 39 getApiTokens: function (callback) {
38 tokenStore.data[token] = data; 40 callback(null, Object.keys(tokenStore.data).filter(function (t) { return t.indexOf(API_TOKEN_PREFIX) === 0; }))
41 },
42 set: function (token, user, callback) {
43 tokenStore.data[token] = user;
39 tokenStore.save(); 44 tokenStore.save();
40 callback(null); 45 callback(null);
41 }, 46 },
@@ -102,7 +107,7 @@ exports.login = function (req, res, next) {
102 verifyUser(req.body.username, req.body.password, function (error, user) { 107 verifyUser(req.body.username, req.body.password, function (error, user) {
103 if (error) return next(new HttpError(401, 'Invalid credentials')); 108 if (error) return next(new HttpError(401, 'Invalid credentials'));
104 109
105 var accessToken = uuid(); 110 var accessToken = LOGIN_TOKEN_PREFIX + uuid();
106 111
107 tokenStore.set(accessToken, user, function (error) { 112 tokenStore.set(accessToken, user, function (error) {
108 if (error) return next(new HttpError(500, error)); 113 if (error) return next(new HttpError(500, error));
@@ -139,6 +144,32 @@ exports.getProfile = function (req, res, next) {
139 next(new HttpSuccess(200, { username: req.user.username })); 144 next(new HttpSuccess(200, { username: req.user.username }));
140}; 145};
141 146
147exports.getTokens = function (req, res, next) {
148 tokenStore.getApiTokens(function (error, result) {
149 if (error) return next(new HttpError(500, error));
150
151 next(new HttpSuccess(200, { accessTokens: result }));
152 });
153};
154
155exports.createToken = function (req, res, next) {
156 var accessToken = API_TOKEN_PREFIX + uuid();
157
158 tokenStore.set(accessToken, req.user, function (error) {
159 if (error) return next(new HttpError(500, error));
160
161 next(new HttpSuccess(201, { accessToken: accessToken }));
162 });
163};
164
165exports.delToken = function (req, res, next) {
166 tokenStore.del(req.params.token, function (error) {
167 if (error) console.error(error);
168
169 next(new HttpSuccess(200, {}));
170 });
171};
172
142// webdav usermanager 173// webdav usermanager
143exports.WebdavUserManager = WebdavUserManager; 174exports.WebdavUserManager = WebdavUserManager;
144 175