Commit c2c00fca by Johannes Zellner

Add access token ui and rest api

1 parent d5940df0
...@@ -89,3 +89,12 @@ a:hover, a:focus { ...@@ -89,3 +89,12 @@ a:hover, a:focus {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.access-token-input {
padding: 5px 0;
width: 450px;
}
.access-token-input > input, .access-token-input i {
cursor: copy !important;
}
...@@ -36,6 +36,21 @@ ...@@ -36,6 +36,21 @@
</span> </span>
</el-dialog> </el-dialog>
<el-dialog title="Access Tokens" :visible.sync="accessTokensDialogVisible" width="30%">
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.
They are shared between all users.
<br/>
<br/>
<div>
<div v-for="accessToken in accessTokens">
<el-input suffix-icon="el-icon-copy-document" v-model="accessToken" class="access-token-input" @focus="onCopyAccessToken" size="small"></el-input>
<el-button icon="el-icon-delete" type="danger" size="small" @click="onDeleteAccessToken(accessToken)"></el-button>
</div>
</div>
<br/>
<el-button @click="onCreateAccessToken()" size="small" type="primary">Create Access Token</el-button>
</el-dialog>
<el-header> <el-header>
<el-row type="flex" justify="space-between"> <el-row type="flex" justify="space-between">
<div style="padding: 7px;"> <div style="padding: 7px;">
...@@ -66,6 +81,7 @@ ...@@ -66,6 +81,7 @@
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item disabled divided>WebDAV Endpoint</el-dropdown-item> <el-dropdown-item disabled divided>WebDAV Endpoint</el-dropdown-item>
<el-dropdown-item><a href="/_webdav/" target="_blank">{{ origin }}/_webdav/</a></el-dropdown-item> <el-dropdown-item><a href="/_webdav/" target="_blank">{{ origin }}/_webdav/</a></el-dropdown-item>
<el-dropdown-item command="apiAccess" divided><i class="el-icon-connection"></i> Access Tokens</el-dropdown-item>
<el-dropdown-item command="about" divided><i class="el-icon-info"></i> About</el-dropdown-item> <el-dropdown-item command="about" divided><i class="el-icon-info"></i> About</el-dropdown-item>
<el-dropdown-item command="logout" id="logoutButton"><i class="el-icon-circle-close"></i> Logout</el-dropdown-item> <el-dropdown-item command="logout" id="logoutButton"><i class="el-icon-circle-close"></i> Logout</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
......
...@@ -43,6 +43,8 @@ function initWithToken(accessToken) { ...@@ -43,6 +43,8 @@ function initWithToken(accessToken) {
app.folderListingEnabled = !!result.body.folderListingEnabled; app.folderListingEnabled = !!result.body.folderListingEnabled;
loadDirectory(decode(window.location.hash.slice(1))); loadDirectory(decode(window.location.hash.slice(1)));
app.refreshAccessTokens();
}); });
}); });
} }
...@@ -278,7 +280,9 @@ var app = new Vue({ ...@@ -278,7 +280,9 @@ var app = new Vue({
password: '', password: '',
busy: false busy: false
}, },
entries: [] entries: [],
accessTokens: [],
accessTokensDialogVisible: false
}, },
methods: { methods: {
onLogin: function () { onLogin: function () {
...@@ -312,6 +316,8 @@ var app = new Vue({ ...@@ -312,6 +316,8 @@ var app = new Vue({
}).then(function () {}).catch(function () {}); }).then(function () {}).catch(function () {});
} else if (command === 'logout') { } else if (command === 'logout') {
logout(); logout();
} else if (command === 'apiAccess') {
this.accessTokensDialogVisible = true;
} }
}, },
onDownload: function (entry) { onDownload: function (entry) {
...@@ -415,6 +421,42 @@ var app = new Vue({ ...@@ -415,6 +421,42 @@ var app = new Vue({
}); });
}).catch(function () {}); }).catch(function () {});
}, },
refreshAccessTokens: function () {
var that = this;
superagent.get('/api/tokens').query({ access_token: localStorage.accessToken }).end(function (error, result) {
if (error && !result) return that.$message.error(error.message);
that.accessTokens = result.body.accessTokens;
});
},
onCopyAccessToken: function (event) {
event.target.select();
document.execCommand('copy');
this.$message({ type: 'success', message: 'Access token copied to clipboard' });
},
onCreateAccessToken: function () {
var that = this;
superagent.post('/api/tokens').query({ access_token: localStorage.accessToken }).end(function (error, result) {
if (error && !result) return that.$message.error(error.message);
that.refreshAccessTokens();
});
},
onDeleteAccessToken: function (token) {
var that = this;
this.$confirm('All actions from apps using this token will fail!', 'Really delete this access token?', { confirmButtonText: 'Yes Delete', cancelButtonText: 'No' }).then(function () {
superagent.delete('/api/tokens/' + token).query({ access_token: localStorage.accessToken }).end(function (error, result) {
if (error && !result) return that.$message.error(error.message);
that.refreshAccessTokens();
});
}).catch(function () {});
},
prettyDate: function (row, column, cellValue, index) { prettyDate: function (row, column, cellValue, index) {
var date = new Date(cellValue), var date = new Date(cellValue),
diff = (((new Date()).getTime() - date.getTime()) / 1000), diff = (((new Date()).getTime() - date.getTime()) / 1000),
......
...@@ -77,6 +77,9 @@ router.post ('/api/login', auth.login); ...@@ -77,6 +77,9 @@ router.post ('/api/login', auth.login);
router.post ('/api/logout', auth.verify, auth.logout); router.post ('/api/logout', auth.verify, auth.logout);
router.get ('/api/settings', auth.verify, getSettings); router.get ('/api/settings', auth.verify, getSettings);
router.put ('/api/settings', auth.verify, setSettings); router.put ('/api/settings', auth.verify, setSettings);
router.get ('/api/tokens', auth.verify, auth.getTokens);
router.post ('/api/tokens', auth.verify, auth.createToken);
router.delete('/api/tokens/:token', auth.verify, auth.delToken);
router.get ('/api/profile', auth.verify, auth.getProfile); router.get ('/api/profile', auth.verify, auth.getProfile);
router.get ('/api/files/*', auth.verify, files.get); router.get ('/api/files/*', auth.verify, files.get);
router.post ('/api/files/*', auth.verify, multipart, files.post); router.post ('/api/files/*', auth.verify, multipart, files.post);
......
...@@ -15,6 +15,8 @@ const LDAP_USERS_BASE_DN = process.env.CLOUDRON_LDAP_USERS_BASE_DN; ...@@ -15,6 +15,8 @@ const LDAP_USERS_BASE_DN = process.env.CLOUDRON_LDAP_USERS_BASE_DN;
const LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json'); const LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json');
const TOKENSTORE_FILE = path.resolve(process.env.TOKENSTORE_FILE || './.tokens.json'); const TOKENSTORE_FILE = path.resolve(process.env.TOKENSTORE_FILE || './.tokens.json');
const AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local'; const AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local';
const LOGIN_TOKEN_PREFIX = 'login-';
const API_TOKEN_PREFIX = 'api-';
if (AUTH_METHOD === 'ldap') { if (AUTH_METHOD === 'ldap') {
console.log('Use ldap auth'); console.log('Use ldap auth');
...@@ -34,8 +36,11 @@ var tokenStore = { ...@@ -34,8 +36,11 @@ var tokenStore = {
get: function (token, callback) { get: function (token, callback) {
callback(tokenStore.data[token] ? null : 'not found', tokenStore.data[token]); callback(tokenStore.data[token] ? null : 'not found', tokenStore.data[token]);
}, },
set: function (token, data, callback) { getApiTokens: function (callback) {
tokenStore.data[token] = data; callback(null, Object.keys(tokenStore.data).filter(function (t) { return t.indexOf(API_TOKEN_PREFIX) === 0; }))
},
set: function (token, user, callback) {
tokenStore.data[token] = user;
tokenStore.save(); tokenStore.save();
callback(null); callback(null);
}, },
...@@ -102,7 +107,7 @@ exports.login = function (req, res, next) { ...@@ -102,7 +107,7 @@ exports.login = function (req, res, next) {
verifyUser(req.body.username, req.body.password, function (error, user) { verifyUser(req.body.username, req.body.password, function (error, user) {
if (error) return next(new HttpError(401, 'Invalid credentials')); if (error) return next(new HttpError(401, 'Invalid credentials'));
var accessToken = uuid(); var accessToken = LOGIN_TOKEN_PREFIX + uuid();
tokenStore.set(accessToken, user, function (error) { tokenStore.set(accessToken, user, function (error) {
if (error) return next(new HttpError(500, error)); if (error) return next(new HttpError(500, error));
...@@ -139,6 +144,32 @@ exports.getProfile = function (req, res, next) { ...@@ -139,6 +144,32 @@ exports.getProfile = function (req, res, next) {
next(new HttpSuccess(200, { username: req.user.username })); next(new HttpSuccess(200, { username: req.user.username }));
}; };
exports.getTokens = function (req, res, next) {
tokenStore.getApiTokens(function (error, result) {
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, { accessTokens: result }));
});
};
exports.createToken = function (req, res, next) {
var accessToken = API_TOKEN_PREFIX + uuid();
tokenStore.set(accessToken, req.user, function (error) {
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(201, { accessToken: accessToken }));
});
};
exports.delToken = function (req, res, next) {
tokenStore.del(req.params.token, function (error) {
if (error) console.error(error);
next(new HttpSuccess(200, {}));
});
};
// webdav usermanager // webdav usermanager
exports.WebdavUserManager = WebdavUserManager; exports.WebdavUserManager = WebdavUserManager;
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!