@@ -14,4 +14,8 @@ var InviteSchema = mongoose.Schema({ | |||
exp: Date | |||
}); | |||
InviteSchema.methods.use = function(canonicalname, cb) { | |||
return this.model('Invite').updateOne({code: this.code}, {recipient: canonicalname, used: Date.now()}, cb); | |||
}; | |||
module.exports = mongoose.model('Invite', InviteSchema); |
@@ -11,6 +11,7 @@ var UploadSchema = mongoose.Schema({ | |||
default: 0 | |||
}, | |||
uploader: String, | |||
uploadKey: String, | |||
date: Date, | |||
file: Object | |||
}); | |||
@@ -7,6 +7,11 @@ var UserSchema = mongoose.Schema({ | |||
unique: true, | |||
required: true | |||
}, | |||
canonicalname: { | |||
type: String, | |||
unique: true, | |||
required: true | |||
}, | |||
scope: [String], | |||
uploadCount: { | |||
type: Number, | |||
@@ -19,6 +24,6 @@ var UserSchema = mongoose.Schema({ | |||
date: Date | |||
}); | |||
UserSchema.plugin(passportLocalMongoose); | |||
UserSchema.plugin(passportLocalMongoose, {usernameField: 'canonicalname'}); | |||
module.exports = mongoose.model('User', UserSchema); |
@@ -58,8 +58,7 @@ fieldset:before { | |||
border: 1px solid #999; | |||
width: 226px; | |||
padding: 12px 12px; | |||
margin: auto; | |||
margin-bottom: 5px; | |||
margin: auto auto 5px; | |||
} | |||
button { | |||
@@ -70,9 +69,8 @@ button { | |||
cursor: pointer; | |||
display: block; | |||
padding: 10px 30px; | |||
margin: auto; | |||
margin-top: 20px; | |||
transition: background 0.25s; | |||
margin: 20px auto auto; | |||
transition: background 0.25s, border-color 0.25s; | |||
} | |||
button:hover { | |||
@@ -81,3 +79,31 @@ button:hover { | |||
text-decoration: none; | |||
outline: none; | |||
} | |||
button.shake { | |||
background: #ff6666; | |||
border-color: #ff6666; | |||
color: #fff; | |||
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; | |||
transform: translate3d(0, 0, 0); | |||
backface-visibility: hidden; | |||
perspective: 1000px; | |||
} | |||
@keyframes shake { | |||
10%, 90% { | |||
transform: translate3d(-1px, 0, 0); | |||
} | |||
20%, 80% { | |||
transform: translate3d(2px, 0, 0); | |||
} | |||
30%, 50%, 70% { | |||
transform: translate3d(-4px, 0, 0); | |||
} | |||
40%, 60% { | |||
transform: translate3d(4px, 0, 0); | |||
} | |||
} |
@@ -8,7 +8,7 @@ body { | |||
a { | |||
position: absolute; | |||
top: 5px; | |||
top: 40px; | |||
left: 48%; | |||
opacity: 0.1; | |||
height: 30px; | |||
@@ -34,8 +34,6 @@ video { | |||
left: 50%; | |||
min-width: 100%; | |||
min-height: 100%; | |||
width: 1920px; | |||
height: 760px; | |||
z-index: -100; | |||
transform: translateX(-50%) translateY(-50%); | |||
background-size: cover; | |||
@@ -1,57 +1,48 @@ | |||
var angular = require('angular'); | |||
angular.module('ApiCtrl', ['ApiSvc', 'AuthSvc']).controller('ApiController', ['$scope', 'ApiService', 'AuthService', function ($scope, ApiService, AuthService) { | |||
// Transforms an array of period-separated properties ex. ["file.upload", "user.view", "user.ban"] | |||
// to json ex. { "file": "upload", "user": ["view", "ban"] } | |||
function splitScope(scope) { | |||
var res = {}; | |||
for (var i in scope) { | |||
var perm = scope[i]; | |||
var prefix = perm.substr(0, perm.indexOf('.')); | |||
var postfix = perm.substr(perm.indexOf('.') + 1); | |||
if (!res[prefix]) res[prefix] = []; | |||
res[prefix].push({name: postfix}); | |||
if (scope.hasOwnProperty(i)) { | |||
var perm = scope[i]; | |||
var prefix = perm.substr(0, perm.indexOf('.')); | |||
var postfix = perm.substr(perm.indexOf('.') + 1); | |||
if (!res[prefix]) res[prefix] = []; | |||
res[prefix].push({name: postfix}); | |||
} | |||
} | |||
return res; | |||
} | |||
$scope.checkCkPerm = function(prefix, perm) { | |||
var index = $scope.scopeObj[prefix].indexOf(perm); | |||
if ($scope.scopeObj[prefix][index].isChecked) { | |||
$scope.ckScope.push(prefix + '.' + perm.name); | |||
} else { | |||
var index = $scope.ckScope.indexOf(prefix + '.' + perm.name); | |||
$scope.ckScope.splice(index, 1); | |||
} | |||
}; | |||
// Called on init, retrieves the user's scope from the server. | |||
$scope.parseScope = function () { | |||
AuthService.currentUser(function (res) { | |||
$scope.scopeObj = splitScope(res.scope); | |||
$scope.ckScope = []; | |||
$scope.currKeyScope = []; | |||
}) | |||
}; | |||
// Triggered when a checkbox for a permission changes. | |||
// Updates the currKeyScope object with the addition or removal. | |||
$scope.updateCurrKeyPerm = function(prefix, perm) { | |||
var index = $scope.scopeObj[prefix].indexOf(perm); | |||
if ($scope.scopeObj[prefix][index].isChecked) { | |||
$scope.currKeyScope.push(prefix + '.' + perm.name); | |||
} else { | |||
index = $scope.currKeyScope.indexOf(prefix + '.' + perm.name); | |||
$scope.currKeyScope.splice(index, 1); | |||
} | |||
}; | |||
$scope.getKeys = function () { | |||
ApiService.getAll(function (keys) { | |||
ApiService.getAllKeys(function (keys) { | |||
$scope.keys = keys; | |||
}); | |||
}; | |||
$scope.hideNewKey = function () { | |||
$scope.nModalShow = false; | |||
}; | |||
$scope.showNewKey = function () { | |||
$scope.nModalShow = true; | |||
}; | |||
$scope.hideKeyInfo = function () { | |||
$scope.kModalShow = false; | |||
}; | |||
$scope.showKeyInfo = function (key) { | |||
$scope.kModalShow = true; | |||
$scope.currKey = key; | |||
$scope.currKey.scopeObj = splitScope($scope.currKey.scope); | |||
}; | |||
$scope.deleteKey = function (key) { | |||
ApiService.deleteKey(key, function () { | |||
var index = $scope.keys.indexOf(key); | |||
@@ -62,17 +53,35 @@ angular.module('ApiCtrl', ['ApiSvc', 'AuthSvc']).controller('ApiController', ['$ | |||
}; | |||
$scope.createKey = function () { | |||
if ($scope.ckScope.length === 0 || !$scope.ckIdentifier) | |||
if ($scope.currKeyScope.length === 0 || !$scope.currKeyIdentifier) | |||
return; | |||
ApiService.createKey({ | |||
identifier: $scope.ckIdentifier, | |||
scope: JSON.stringify($scope.ckScope) | |||
identifier: $scope.currKeyIdentifier, | |||
scope: JSON.stringify($scope.currKeyScope) | |||
}, function (res) { | |||
if (res.key) { | |||
$scope.hideNewKey(); | |||
$scope.getKeys(); | |||
} | |||
}); | |||
} | |||
}; | |||
// Hide/show new key modal dialog | |||
$scope.hideNewKey = function () { | |||
$scope.nModalShow = false; | |||
}; | |||
$scope.showNewKey = function () { | |||
$scope.nModalShow = true; | |||
}; | |||
// Hide/show key info modal dialog | |||
$scope.hideKeyInfo = function () { | |||
$scope.kModalShow = false; | |||
}; | |||
$scope.showKeyInfo = function (key) { | |||
$scope.kModalShow = true; | |||
$scope.currKey = key; | |||
$scope.currKey.scopeObj = splitScope($scope.currKey.scope); | |||
}; | |||
}]); |
@@ -0,0 +1,64 @@ | |||
var angular = require('angular'); | |||
angular.module('InviteCtrl', ['InviteSvc', 'AuthSvc']).controller('InviteController', ['$scope', 'InviteService', 'AuthService', function($scope, InviteService, AuthService) { | |||
// Transforms an array of period-separated properties ex. ["file.upload", "user.view", "user.ban"] | |||
// to json ex. { "file": "upload", "user": ["view", "ban"] } | |||
function splitScope(scope) { | |||
var res = {}; | |||
for (var i in scope) { | |||
if (scope.hasOwnProperty(i)) { | |||
var perm = scope[i]; | |||
var prefix = perm.substr(0, perm.indexOf('.')); | |||
var postfix = perm.substr(perm.indexOf('.') + 1); | |||
if (!res[prefix]) | |||
res[prefix] = []; | |||
res[prefix].push({name: postfix}); | |||
} | |||
} | |||
return res; | |||
} | |||
// Called on init, retrieves the user's scope from the server. | |||
$scope.parseScope = function () { | |||
AuthService.currentUser(function (res) { | |||
$scope.scopeObj = splitScope(res.scope); | |||
$scope.currInvScope = []; | |||
}) | |||
}; | |||
// Triggered when a checkbox for a permission changes. | |||
// Updates the currInvScope object with the addition or removal. | |||
$scope.updateCurrInvPerm = function(prefix, perm) { | |||
var index = $scope.scopeObj[prefix].indexOf(perm); | |||
if ($scope.scopeObj[prefix][index].isChecked) { | |||
$scope.currInvScope.push(prefix + '.' + perm.name); | |||
} else { | |||
index = $scope.currInvScope.indexOf(prefix + '.' + perm.name); | |||
$scope.currInvScope.splice(index, 1); | |||
} | |||
}; | |||
$scope.getInvites = function() { | |||
InviteService.getAllInvites(function(invites) { | |||
$scope.invites = invites; | |||
}); | |||
}; | |||
$scope.deleteInvite = function(invite) { | |||
InviteService.deleteInvite(function() { | |||
var index = $scope.invites.indexOf(invite); | |||
$scope.invites.splice(index, 1); | |||
}); | |||
}; | |||
$scope.createInvite = function() { | |||
InviteService.createInvite({ | |||
scope: JSON.stringify($scope.currInvScope), | |||
exp: JSON.stringify($scope.currInvExpiry) | |||
}, function(res) { | |||
if (res.code) { | |||
$scope.getInvites(); | |||
} | |||
}); | |||
}; | |||
}]); |
@@ -3,7 +3,7 @@ var angular = require('angular'); | |||
angular.module('NavCtrl', ['AuthSvc']).controller('NavController', ['$scope', '$window', 'AuthService', function($scope, $window, AuthService) { | |||
$scope.user = {}; | |||
AuthService.currentUser(function(user) { | |||
$scope.user = user; | |||
$scope.user = username; | |||
}); | |||
$scope.logout = AuthService.logout; | |||
@@ -12,5 +12,4 @@ angular.module('NavCtrl', ['AuthSvc']).controller('NavController', ['$scope', '$ | |||
if (!$scope.user.scope) return false; | |||
return $scope.user.scope.indexOf(permission) !== -1; | |||
}; | |||
}]); |
@@ -0,0 +1,9 @@ | |||
var angular = require('angular'); | |||
angular.module('UserCtrl', ['UserSvc']).controller('UserController', ['$scope', 'UserService', function($scope, UserService) { | |||
$scope.getUsers = function() { | |||
UserService.getAllUsers(function(users) { | |||
$scope.users = users; | |||
}); | |||
}; | |||
}]); |
@@ -1,6 +1,6 @@ | |||
var angular = require('angular'); | |||
var uirouter = require('angular-ui-router'); | |||
var app = angular.module('shimapan-panel', ['ui.router', 'AuthSvc', 'ApiSvc', 'ApiCtrl', 'NavCtrl', 'PanelRoutes']); | |||
var app = angular.module('shimapan-panel', ['ui.router', 'AuthSvc', 'ApiSvc', 'InviteSvc', 'UserSvc', 'ApiCtrl', 'InviteCtrl', 'UserCtrl', 'NavCtrl', 'PanelRoutes']); | |||
app.run(['$rootScope', '$state', '$stateParams', function($rootScope, $state, $stateParams) { | |||
$rootScope.$state = $state; | |||
@@ -1,6 +1,6 @@ | |||
var angular = require('angular'); | |||
angular.module('ApiSvc', []).service('ApiService', ['$http', '$window', function ($http, $window) { | |||
angular.module('ApiSvc', []).service('ApiService', ['$http', function ($http) { | |||
this.getKey = function (identifier, cb) { | |||
$http({ | |||
method: 'GET', | |||
@@ -11,7 +11,7 @@ angular.module('ApiSvc', []).service('ApiService', ['$http', '$window', function | |||
}); | |||
}; | |||
this.getAll = function (cb) { | |||
this.getAllKeys = function (cb) { | |||
$http({ | |||
method: 'GET', | |||
url: '/api/keys/get' | |||
@@ -28,7 +28,8 @@ angular.module('ApiSvc', []).service('ApiService', ['$http', '$window', function | |||
transformRequest: function (obj) { | |||
var str = []; | |||
for (var p in obj) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: {key: key.key} | |||
@@ -45,7 +46,8 @@ angular.module('ApiSvc', []).service('ApiService', ['$http', '$window', function | |||
transformRequest: function (obj) { | |||
var str = []; | |||
for (var p in obj) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: key | |||
@@ -9,13 +9,14 @@ angular.module('AuthSvc', []).service('AuthService', ['$http', '$window', functi | |||
transformRequest: function(obj) { | |||
var str = []; | |||
for (var p in obj) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: user | |||
}).then(function(res) { | |||
if (res.status === 401) return false; | |||
$window.location.href = '/home'; | |||
if (res.status === 200) | |||
$window.location.href = '/home'; | |||
}) | |||
}; | |||
@@ -36,13 +37,14 @@ angular.module('AuthSvc', []).service('AuthService', ['$http', '$window', functi | |||
transformRequest: function(obj) { | |||
var str = []; | |||
for (var p in obj) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: user | |||
}).then(function(res) { | |||
if (res.status === 401) return false; | |||
$window.location.href = '/home'; | |||
if (res.status === 200) | |||
$window.location.href = '/home'; | |||
}); | |||
}; | |||
@@ -0,0 +1,58 @@ | |||
var angular = require('angular'); | |||
angular.module('InviteSvc', []).service('InviteService', ['$http', function ($http) { | |||
this.getInvite = function (code, cb) { | |||
$http({ | |||
method: 'GET', | |||
url: '/api/invites/get', | |||
params: {code: code} | |||
}).then(function (res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
this.getAllInvites = function (cb) { | |||
$http({ | |||
method: 'GET', | |||
url: '/api/invites/get' | |||
}).then(function (res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
this.deleteInvite = function (invite, cb) { | |||
$http({ | |||
method: 'POST', | |||
url: '/api/invites/delete', | |||
headers: {'Content-Type': 'application/x-www-form-urlencode'}, | |||
transformRequest: function (obj) { | |||
var str = []; | |||
for (var p in obj) | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: {code: invite.code} | |||
}).then(function (res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
this.createInvite = function (invite, cb) { | |||
$http({ | |||
method: 'POST', | |||
url: '/api/invites/create', | |||
headers: {'Content-Type': 'application/x-www-form-urlencode'}, | |||
transformRequest: function (obj) { | |||
var str = []; | |||
for (var p in obj) | |||
if (obj.hasOwnProperty(p)) | |||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | |||
return str.join("&"); | |||
}, | |||
data: invite | |||
}).then(function(res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
}]); |
@@ -0,0 +1,22 @@ | |||
var angular = require('angular'); | |||
angular.module('UserSvc', []).service('UserService', ['$http', function($http) { | |||
this.getUser = function(username, cb) { | |||
$http({ | |||
method: 'GET', | |||
url: '/api/users/get', | |||
params: {username: username} | |||
}).then(function(res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
this.getAllUsers = function(cb) { | |||
$http({ | |||
method: 'GET', | |||
url: '/api/users/get' | |||
}).then(function(res) { | |||
cb(res.data); | |||
}); | |||
}; | |||
}]); |
@@ -2,11 +2,16 @@ var angular = require('angular'); | |||
angular.module('LoginComp', ['AuthSvc']).component('loginComponent', { | |||
templateUrl: '/views/shimapan/login-form.html', | |||
controller: ['$scope', 'AuthService', function ($scope, AuthService) { | |||
controller: ['$scope', '$timeout', 'AuthService', function ($scope, $timeout, AuthService) { | |||
$scope.login = function () { | |||
AuthService.login({ | |||
username: $scope.username, | |||
password: $scope.password | |||
}).catch(function() { | |||
$scope.error = true; | |||
$timeout(function() { | |||
$scope.error = false; | |||
}, 820); | |||
}); | |||
}; | |||
}] |
@@ -2,12 +2,17 @@ var angular = require('angular'); | |||
angular.module('RegisterComp', ['AuthSvc']).component('registerComponent', { | |||
templateUrl: '/views/shimapan/register-form.html', | |||
controller: ['$scope', 'AuthService', function ($scope, AuthService) { | |||
controller: ['$scope', '$timeout', 'AuthService', function ($scope, $timeout, AuthService) { | |||
$scope.register = function () { | |||
AuthService.register({ | |||
username: $scope.username, | |||
password: $scope.password, | |||
invite: $scope.invite | |||
}).catch(function() { | |||
$scope.error = true; | |||
$timeout(function() { | |||
$scope.error = false | |||
}, 820); | |||
}); | |||
}; | |||
}] |
@@ -2,7 +2,7 @@ var angular = require('angular'); | |||
angular.module('UploadComp', ['ngFileUpload', 'AuthSvc']).component('uploadComponent', { | |||
templateUrl: '/views/shimapan/upload-form.html', | |||
controller: ['$scope', 'Upload', '$timeout', 'AuthService', function ($scope, Upload, $timeout, AuthService) { | |||
controller: ['$scope', 'Upload', '$timeout', function ($scope, Upload, $timeout) { | |||
$scope.errToString = function (err) { | |||
if (err === 'maxSize') | |||
return "File too large."; | |||
@@ -1,6 +1,3 @@ | |||
var fs = require('fs'); | |||
var path = require('path'); | |||
var express = require('express'); | |||
var router = express.Router(); | |||
@@ -9,57 +6,124 @@ var Invite = require('../models/Invite.js'); | |||
var passport = require('passport'); | |||
function checkInvite(code, callback) { | |||
var async = require('async'); | |||
// Normalizes, decomposes, and lowercases a unicode string | |||
function canonicalize(username) { | |||
return username.normalize('NFKD').toLowerCase(); | |||
} | |||
// Checks if an invite code is valid | |||
// Returns the invite object if valid | |||
function checkInvite(code, cb) { | |||
Invite.findOne({code: code}, function (err, invite) { | |||
if (err) return callback(err); | |||
if (!invite || invite.used || invite.exp < new Date()) | |||
callback(null, false); | |||
if (err) | |||
cb(err); | |||
else if (!invite) | |||
cb('Invalid invite code.'); | |||
else if (invite.used) | |||
cb('Invite already used.'); | |||
else if (invite.exp < Date.now()) | |||
cb('Invite expired.'); | |||
else | |||
callback(null, true, invite); | |||
cb(null, invite); | |||
}); | |||
} | |||
// Validates the username, then registers the user in the database using the given invite. | |||
function registerUser(username, password, invite, sanitizeFn, cb) { | |||
async.series([ | |||
function (cb) { | |||
// Canonicalize and sanitize the username, checking for HTML | |||
var canonicalName = canonicalize(username); | |||
var sanitizedName = sanitizeFn(canonicalName); | |||
if (sanitizedName !== canonicalName) | |||
cb('Username failed sanitization check.'); | |||
else if (canonicalName.length > 36) | |||
cb('Username too long.'); | |||
else | |||
cb(null); | |||
}, | |||
function (cb) { | |||
User.register(new User({ | |||
username: username, | |||
canonicalname: canonicalize(username), | |||
scope: invite.scope, | |||
date: Date.now() | |||
}), password, cb); | |||
}, | |||
function (cb) { | |||
invite.use(canonicalize(username), cb); | |||
} | |||
], function (err) { | |||
cb(err); | |||
}); | |||
} | |||
function useInvite(code, username) { | |||
Invite.updateOne({code: code}, {recipient: username, used: new Date()}, function (err) { | |||
if (err) throw err; | |||
// Authenticates and creates the required session variables | |||
function setupSession(username, req, res, cb) { | |||
// Body needs to contain canonical name for proper authentication | |||
req.body.canonicalname = canonicalize(req.body.username); | |||
passport.authenticate('local')(req, res, function () { | |||
req.session.save(function (err) { | |||
if (!err) { | |||
req.session.passport.username = username; | |||
req.session.passport.canonicalname = canonicalize(username); | |||
} | |||
cb(err); | |||
}); | |||
}); | |||
} | |||
router.post('/register', function (req, res, next) { | |||
// Validate the invite code, then hand off to passport | |||
checkInvite(req.body.invite, function (err, valid, invite) { | |||
if (valid) { | |||
User.register( | |||
new User({username: req.body.username, scope: invite.scope, date: Date.now()}), | |||
req.body.password, | |||
function (err) { | |||
if (err) return res.status(403).json({'message': err.message}); | |||
passport.authenticate('local')(req, res, function () { | |||
req.session.save(function(err) { | |||
if (err) return next(err); | |||
useInvite(req.body.invite, req.body.username); | |||
req.session.username = req.body.username; | |||
res.status(200).json({'message': 'Registered.'}); | |||
}); | |||
}); | |||
} | |||
); | |||
router.post('/register', function (req, res) { | |||
async.waterfall([ | |||
function (cb) { | |||
checkInvite(req.body.invite, cb); | |||
}, | |||
function (invite, cb) { | |||
registerUser(req.body.username, req.body.password, invite, req.sanitize, cb); | |||
}, | |||
function (cb) { | |||
setupSession(req.body.username, req, res, cb); | |||
} | |||
], function (err) { | |||
if (err) { | |||
res.status(401).json({'message': err}); | |||
} else { | |||
res.status(401).json({'message': 'Invalid invite code.'}); | |||
res.status(200).json({'message': 'Registration successful.'}); | |||
} | |||
}); | |||
}); | |||
router.post('/login', function (req, res, next) { | |||
passport.authenticate('local', function(err, user, info) { | |||
if (err) return next(err); | |||
if (!user) return res.status(401).json({'message': info}); | |||
req.logIn(user, function(err) { | |||
if (err) return next(err); | |||
req.session.username = user; | |||
res.status(200).json({'message': 'Logged in.'}); | |||
}); | |||
})(req, res, next); | |||
// Take 'username' from the form and canonicalize it for authentication. | |||
req.body.canonicalname = canonicalize(req.body.username); | |||
async.waterfall([ | |||
function (cb) { | |||
passport.authenticate('local', function(err, user, info) { | |||
cb(err, user, info); | |||
})(req, res, next); | |||
}, | |||
function (user, info, cb) { | |||
if (!user) | |||
cb(info); | |||
else | |||
req.logIn(user, cb); | |||
}, | |||
function (cb) { | |||
req.session.passport.username = req.body.username; | |||
req.session.passport.canonicalname = canonicalize(req.body.username); | |||
cb(); | |||
} | |||
], function (err) { | |||
if (err) | |||
res.status(401).json({'message': err}); | |||
else | |||
res.status(200).json({'message': 'Login successful.'}); | |||
}); | |||
}); | |||
router.get('/logout', function (req, res) { | |||
@@ -67,17 +131,19 @@ router.get('/logout', function (req, res) { | |||
res.status(200).json({'message': 'Logged out.'}); | |||
}); | |||
router.get('/session', function(req, res) { | |||
if (req.session.passport.user) { | |||
User.findOne({username: req.session.passport.user}, function(err, user) { | |||
res.status(200).json({ | |||
user: user.username, | |||
scope: user.scope | |||
}); | |||
}); | |||
} else { | |||
res.status(401).json({'message': 'Unauthorized.'}); | |||
} | |||
router.get('/session', function (req, res) { | |||
console.log(req.session.passport); | |||
if (req.session.passport.canonicalname) { | |||
User.findOne({canonicalname: req.session.passport.canonicalname}, function (err, user) { | |||
res.status(200).json({ | |||
username: user.username, | |||
canonicalname: user.canonicalname, | |||
scope: user.scope | |||
}); | |||
}); | |||
} else { | |||
res.status(401).json({'message': 'Unauthorized.'}); | |||
} | |||
}); | |||
module.exports = router; |
@@ -0,0 +1,78 @@ | |||
var express = require('express'); | |||
var router = express.Router(); | |||
var Invite = require('../models/Invite.js'); | |||
var requireScope = function (perm) { | |||
return function(req, res, next) { | |||
User.findOne({username: req.session.passport.user}, function(err, user) { | |||
if (err) throw err; | |||
if (user.scope.indexOf(perm) === -1) | |||
res.status(400).json({'message': 'No permission.'}); | |||
else | |||
next(); | |||
}); | |||
} | |||
}; | |||
router.post('/create', function (req, res) { | |||
if (!req.body.scope) { | |||
res.status(400).json({'message': 'Bad request.'}); | |||
return; | |||
} | |||
var scope; | |||
try { | |||
scope = JSON.parse(req.body.scope); | |||
} catch (e) { | |||
res.status(500).json({'message': e.name + ': ' + e.message}); | |||
return; | |||
} | |||
var expiry = req.body.exp; | |||
if (!expiry || expiry < Date.now()) | |||
expiry = 0; | |||
var entry = { | |||
code: crypto.randomBytes(12).toString('hex'), | |||
scope: scope, | |||
issuer: req.session.passport.user, | |||
issued: Date.now(), | |||
exp: expiry | |||
}; | |||
Invite.create(entry, function (err) { | |||
if (err) { | |||
throw err; | |||
} else { | |||
res.status(200).json({ | |||
code: entry.code, | |||
scope: entry.scope | |||
}); | |||
} | |||
}) | |||
}); | |||
router.get('/get', function (req, res, next) { | |||
var query = {issuer: req.session.passport.user}; | |||
if (req.body.code) | |||
query.code = req.body.code; | |||
Invite.find(query, function (err, invites) { | |||
if (err) { | |||
next(err); | |||
} else { | |||
res.status(200).json(invites); | |||
} | |||
}) | |||
}); | |||
router.post('/delete', function (req, res, next) { | |||
Invite.deleteOne({code: req.body.code}, function (err) { | |||
if (err) next(err); | |||
else res.status(200).json({'message': 'Successfully deleted.'}); | |||
}); | |||
}); | |||
module.exports = router; |
@@ -2,9 +2,22 @@ var express = require('express'); | |||
var router = express.Router(); | |||
var crypto = require('crypto'); | |||
var User = require('../models/User.js'); | |||
var Key = require('../models/Key.js'); | |||
router.post('/create', function (req, res) { | |||
var requireScope = function (perm) { | |||
return function(req, res, next) { | |||
User.findOne({username: req.session.passport.user}, function(err, user) { | |||
if (err) throw err; | |||
if (user.scope.indexOf(perm) === -1) | |||
res.status(400).json({'message': 'No permission.'}); | |||
else | |||
next(); | |||
}); | |||
} | |||
}; | |||
router.post('/create', requireScope('api.create'), function (req, res) { | |||
if (!req.body.identifier || !req.body.scope) { | |||
res.status(400).json({'message': 'Bad request.'}); | |||
return; | |||
@@ -20,7 +33,7 @@ router.post('/create', function (req, res) { | |||
try { | |||
scope = JSON.parse(req.body.scope); | |||
} catch (e) { | |||
res.status(400).json({'message': e.name + ': ' + e.message}); | |||
res.status(500).json({'message': e.name + ': ' + e.message}); | |||
return; | |||
} | |||
@@ -64,7 +77,7 @@ router.get('/get', function (req, res, next) { | |||
}) | |||
}); | |||
router.post('/delete', function(req, res, next) { | |||
router.post('/delete', requireScope('api.delete'), function(req, res, next) { | |||
Key.deleteOne({key: req.body.key}, function(err) { | |||
if (err) next(err); | |||
else res.status(200).json({'message': 'Successfully deleted.'}); | |||
@@ -2,7 +2,7 @@ var express = require('express'); | |||
var router = express.Router(); | |||
var path = require('path'); | |||
router.get('/', function(req, res, next) { | |||
router.get('/', function(req, res) { | |||
res.sendFile(path.join(__dirname, '../../public/views', 'login.html')); | |||
}); | |||
@@ -2,7 +2,7 @@ var express = require('express'); | |||
var router = express.Router(); | |||
var path = require('path'); | |||
router.get('/', function(req, res, next) { | |||
router.get('/', function(req, res) { | |||
res.sendFile(path.join(__dirname, '../../public/views', 'panel.html')); | |||
}); | |||
@@ -2,7 +2,7 @@ var express = require('express'); | |||
var router = express.Router(); | |||
var path = require('path'); | |||
router.get('/', function(req, res, next) { | |||
router.get('/', function(req, res) { | |||
res.sendFile(path.join(__dirname, '../../public/views', 'register.html')); | |||
}); | |||
@@ -7,30 +7,57 @@ var register = require('./register.js'); | |||
var login = require('./login.js'); | |||
var panel = require('./panel.js'); | |||
var keys = require('./keys.js'); | |||
var invites = require('./invites.js'); | |||
var users = require('./users.js'); | |||
var fs = require('fs'); | |||
var path = require('path'); | |||
var Key = require('../models/Key.js'); | |||
var requireLogin = function(req, res, next) { | |||
var checkApiKey = function (key, cb) { | |||
Key.find({key: key}, function (err, res) { | |||
if (err) throw err; | |||
cb(res.length === 1); | |||
}); | |||
}; | |||
var requireLogin = function (req, res, next) { | |||
if (!req.session || !req.session.passport) | |||
return res.redirect('/login'); | |||
else | |||
return next(); | |||
}; | |||
module.exports = function(app) { | |||
var requireLoginApi = function(req, res, next) { | |||
if (!req.session || !req.session.passport) { | |||
if (!req.body.apikey) { | |||
return res.redirect('/login'); | |||
} else { | |||
checkApiKey(res.body.apikey, function(valid) { | |||
if (!valid) | |||
return res.sendStatus(401); | |||
else | |||
return next(); | |||
}); | |||
} | |||
} else { | |||
return next(); | |||
} | |||
}; | |||
module.exports = function (app) { | |||
app.use('/', index); | |||
app.use('/home', requireLogin, home); | |||
app.use('/v', view); | |||
app.use('/api/upload', upload); | |||
app.use('/api/auth', auth); | |||
app.use('/api/keys', requireLogin, keys); | |||
app.use('/api/invites', requireLogin, invites); | |||
app.use('/api/users', requireLogin, users); | |||
app.use('/register', register); | |||
app.use('/login', login); | |||
app.use('/panel', requireLogin, panel); | |||
app.use('/panel*', requireLogin, panel); | |||
app.use(function(err, req, res, next) { | |||
app.use(function (err, req, res) { | |||
if (err.name === 'UnauthorizedError') { | |||
res.status(401); | |||
res.json({"message": err.name + ": " + err.message}); | |||
@@ -4,6 +4,7 @@ var router = express.Router(); | |||
var mongoose = require('mongoose'); | |||
var User = require('../models/User.js'); | |||
var Upload = require('../models/Upload.js'); | |||
var Key = require('../models/Key.js'); | |||
var multer = require('multer'); | |||
var dest = multer({dest: 'uploads/'}); | |||
@@ -14,12 +15,6 @@ function fileNameExists(name) { | |||
}); | |||
} | |||
function updateUserStats(user, size) { | |||
User.updateOne({username: user}, {$inc: {uploadCount: 1, uploadSize: size}}, function (err, res) { | |||
if (err) throw err; | |||
}); | |||
} | |||
function genFileName() { | |||
var charset = "abcdefghijklmnopqrstuvwxyz"; | |||
do { | |||
@@ -30,32 +25,90 @@ function genFileName() { | |||
return chars.join(''); | |||
} | |||
router.post('/', dest.single('file'), function (req, res) { | |||
// Size must be below 128 Megabytes (1024*1024*128 Bytes) | |||
if (req.file.size >= 134217728) { | |||
res.status(413).json({'message': 'File too large.'}); | |||
return; | |||
function updateStats(type, id, size) { | |||
if (type === 'session') { | |||
User.updateOne({username: id}, {$inc: {uploadCount: 1, uploadSize: size}}, function (err) { | |||
if (err) throw err; | |||
}); | |||
} else if (type === 'apikey') { | |||
Key.updateOne({key: id}, {$inc: {uploadCount: 1, uploadSize: size}}, function (err) { | |||
if (err) throw err; | |||
}); | |||
} | |||
} | |||
updateUserStats(req.session.passport.user, req.file.size); | |||
var checkApiKey = function (key, cb) { | |||
Key.find({key: key}, function (err, res) { | |||
if (err) throw err; | |||
cb(res.length === 1, res); | |||
}); | |||
}; | |||
var checkScope = function (type, id, perm, cb) { | |||
if (type === 'session') { | |||
User.findOne({username: id}, function (err, user) { | |||
if (err) throw err; | |||
cb(user.scope.indexOf(perm) !== -1); | |||
}); | |||
} else { | |||
Key.findOne({key: id}, function (err, key) { | |||
if (err) throw err; | |||
cb(key.scope.indexOf(perm) !== -1); | |||
}); | |||
} | |||
}; | |||
var entry = { | |||
name: genFileName(), | |||
uploader: req.session.passport.user, | |||
created: Date.now(), | |||
file: req.file | |||
}; | |||
function uploadFile(req, res, type, key) { | |||
if (!req.file) | |||
return res.status(400).json({'message': 'No file specified.'}); | |||
Upload.create(entry, function (err, next) { | |||
if (err) { | |||
next(err); | |||
} else { | |||
res.send({ | |||
// Size must be below 128 Megabytes (1024*1024*128 Bytes) | |||
if (req.file.size >= 134217728) | |||
return res.status(413).json({'message': 'File too large.'}); | |||
var uploader = type === 'session' ? req.session.passport.user : key[0].username; | |||
var uploadKey = type === 'apikey' ? key[0].key : null; | |||
var id = type === 'session' ? req.session.passport.user : key[0].key; | |||
checkScope(type, id, 'file.upload', function (valid) { | |||
if (!valid) | |||
return res.status(403).json({'message': 'No permission.'}); | |||
var entry = { | |||
name: genFileName(), | |||
uploader: uploader, | |||
uploadKey: uploadKey, | |||
date: Date.now(), | |||
file: req.file | |||
}; | |||
updateStats(type, id, req.file.size); | |||
Upload.create(entry, function (err) { | |||
if (err) throw err; | |||
res.status(200).json({ | |||
name: entry.name, | |||
url: 'https://shimapan.rocks/v/' + entry.name | |||
}); | |||
} | |||
}); | |||
}); | |||
} | |||
router.post('/', dest.single('file'), function (req, res) { | |||
if (!req.session || !req.session.passport) { | |||
if (!req.body.apikey) { | |||
return res.sendStatus(401); | |||
} else { | |||
checkApiKey(req.body.apikey, function (valid, key) { | |||
if (!valid) | |||
return res.sendStatus(401); | |||
else | |||
uploadFile(req, res, 'apikey', key); | |||
}); | |||
} | |||
} else { | |||
uploadFile(req, res, 'session'); | |||
} | |||
}); | |||
module.exports = router; |
@@ -0,0 +1,33 @@ | |||
var express = require('express'); | |||
var router = express.Router(); | |||
var User = require('../models/User.js'); | |||
var requireScope = function (perm) { | |||
return function(req, res, next) { | |||
User.findOne({username: req.session.passport.user}, function(err, user) { | |||
if (err) throw err; | |||
if (user.scope.indexOf(perm) === -1) | |||
res.status(400).json({'message': 'No permission.'}); | |||
else | |||
next(); | |||
}); | |||
} | |||
}; | |||
router.get('/get', requireScope('users.view'), function (req, res, next) { | |||
var query = {}; | |||
if (req.body.username) | |||
query.username = req.body.username; | |||
User.find(query, function (err, users) { | |||
if (err) { | |||
next(err) | |||
} else { | |||
res.status(200).json(users); | |||
} | |||
}) | |||
}); | |||
module.exports = router; |
@@ -105,7 +105,9 @@ gulp.task('concatjs', function () { | |||
var tasks = files.map(function (entry) { | |||
return gulp.src(entry.src) | |||
.pipe(concat(entry.name)) | |||
.pipe(uglify()) | |||
.pipe(uglify().on('error', function(err) { | |||
console.log(err.toString()); | |||
})) | |||
.pipe(gulp.dest('public/js')); | |||
}); | |||
@@ -15,9 +15,7 @@ | |||
</video> | |||
<script> | |||
var videos = [ | |||
"/img/edge.mp4", | |||
"/img/endthis.mp4", | |||
"/img/hell.mp4" | |||
"/img/edge.webm" | |||
]; | |||
var video = document.createElement("source"); | |||
video.setAttribute("src", videos[Math.floor(Math.random() * videos.length)]); | |||
@@ -12,13 +12,13 @@ | |||
<div class="sidebar" ng-controller="NavController"> | |||
<div class="sidebar-title" ng-click="$state.go('home')"><span>Shimapan</span></div> | |||
<ul class="nav"> | |||
<li><a ui-sref="dashboard" ng-class="{active: $state.$current.name=='dashboard'}">Dashboard</a></li> | |||
<li><a ui-sref="search" ng-class="{active: $state.$current.name=='search'}">Search</a></li> | |||
<li><a ui-sref="api" ng-class="{active: $state.$current.name=='api'}">API</a></li> | |||
<li><a ui-sref="invites" ng-class="{active: $state.$current.name=='invites'}">Invites</a></li> | |||
<li ng-hide="!hasPermission('users.view')"><a ui-sref="users" ng-class="{active: $state.$current.name=='users'}">Users</a></li> | |||
<li><a ui-sref="stats" ng-class="{active: $state.$current.name=='stats'}">Statistics</a></li> | |||
<li><a ng-click="logout()">Logout</a></li> | |||
<li><a draggable="false" ui-sref="dashboard" ng-class="{active: $state.$current.name=='dashboard'}">Dashboard</a></li> | |||
<li><a draggable="false" ui-sref="search" ng-class="{active: $state.$current.name=='search'}">Search</a></li> | |||
<li><a draggable="false" ui-sref="api" ng-class="{active: $state.$current.name=='api'}">API</a></li> | |||
<li><a draggable="false" ui-sref="invites" ng-class="{active: $state.$current.name=='invites'}">Invites</a></li> | |||
<li ng-hide="!hasPermission('users.view')"><a draggable="false" ui-sref="users" ng-class="{active: $state.$current.name=='users'}">Users</a></li> | |||
<li><a draggable="false" ui-sref="stats" ng-class="{active: $state.$current.name=='stats'}">Statistics</a></li> | |||
<li><a draggable="false" ng-click="logout()">Logout</a></li> | |||
</ul> | |||
</div> | |||
<div class="content" ng-class="{isOpen: open}"> | |||
@@ -23,7 +23,7 @@ | |||
<p>This key can be used with any 3rd party program or service to upload to and manage your account | |||
with Shimapan.</p> | |||
<p>For example, it can be used in a bash script to upload from the command line:</p> | |||
<pre>APIKEY=[Your API Key Here]<br/>URL=$(curl -s -F "apikey=$APIKEY" -F "file=@$1" https://shimapan.rocks/api/upload | grep url | awk '{print $2}')<br/>echo $URL | tr -d '[\\\,"\n]'</pre> | |||
<pre>APIKEY=[Your API Key Here]<br/>URL=$(curl -s -F "apikey=$APIKEY" -F "file=@$1" https://shimapan.rocks/api/upload | grep -Po '"'"url"'"\s*:\s*"\K([^"]*)'<br/>echo $URL</pre> | |||
<br/> | |||
<p>Key Permissions:</p> | |||
<table> | |||
@@ -54,14 +54,14 @@ | |||
</div> | |||
<div class="modal-body"> | |||
<p>Identifier to describe the purpose/use of this key:</p> | |||
<input id="identifier" placeholder="Identifier" class="form-control" type="text" ng-model="ckIdentifier"/> | |||
<input id="identifier" placeholder="Identifier" class="form-control" type="text" ng-model="currKeyIdentifier"/> | |||
<br/> | |||
<p>Permissions the key should have:</p> | |||
<table> | |||
<tr ng-repeat="(prefix, perms) in scopeObj"> | |||
<th>{{prefix}}:</th> | |||
<td ng-repeat="perm in perms"> | |||
<input type="checkbox" name="{{perm.name}}" ng-model="perm.isChecked" ng-change="checkCkPerm(prefix, perm)"/> | |||
<input type="checkbox" title="{{perm.name}}" name="{{perm.name}}" ng-model="perm.isChecked" ng-change="updateCurrKeyPerm(prefix, perm)"/> | |||
<label for="{{perm.name}}" ng-bind="perm.name"></label> | |||
</td> | |||
</tr> | |||
@@ -0,0 +1,14 @@ | |||
<div class="inner" ng-controller="InviteController" ng-init="getInvites()"> | |||
<table class="invites"> | |||
<tr> | |||
<th>Code</th> | |||
<th>Scope</th> | |||
<th>Expiry</th> | |||
</tr> | |||
<tr ng-repeat="invite in invites"> | |||
<td>{{invite.code}}</td> | |||
<td>{{invite.scope}}</td> | |||
<td>{{invite.expiry}}</td> | |||
</tr> | |||
</table> | |||
</div> |
@@ -0,0 +1,18 @@ | |||
<div class="inner" ng-controller="UserController" ng-init="getUsers()"> | |||
<table class="users"> | |||
<tr> | |||
<th>Username</th> | |||
<th>Scope</th> | |||
<th>Upload Count</th> | |||
<th>Upload Size</th> | |||
<th>Date</th> | |||
</tr> | |||
<tr ng-repeat="user in users"> | |||
<td>{{user.username}}</td> | |||
<td>{{user.scope}}</td> | |||
<td>{{user.uploadCount}}</td> | |||
<td>{{user.uploadSize}}</td> | |||
<td>{{user.date}}</td> | |||
</tr> | |||
</table> | |||
</div> |
@@ -4,7 +4,7 @@ | |||
<form ng-submit="login()"> | |||
<input id="username" placeholder="Username" class="form-control" type="text" ng-model="username"/> | |||
<input id="password" placeholder="Password" class="form-control" type="password" ng-model="password"/> | |||
<button type="submit" class="btn">Submit</button> | |||
<button type="submit" class="btn" ng-class="{shake: error}">Submit</button> | |||
</form> | |||
</fieldset> | |||
</div> |
@@ -5,7 +5,7 @@ | |||
<input id="username" placeholder="Username" class="form-control" type="text" ng-model="username"/> | |||
<input id="password" placeholder="Password" class="form-control" type="password" ng-model="password"/> | |||
<input id="invite" placeholder="Invite Code" class="form-control" type="text" ng-model="invite"/> | |||
<button type="submit" class="btn">Submit</button> | |||
<button type="submit" class="btn" ng-class="{shake: error}">Submit</button> | |||
</form> | |||
</fieldset> | |||
</div> |
@@ -50,7 +50,6 @@ app.use(bodyParser.urlencoded({ extended: true })); | |||
app.use(bodyParser.text()); | |||
app.use(sanitizer()); | |||
app.use(methodOverride('X-HTTP-Method-Override')); | |||
app.use(passport.initialize()); | |||
//app.use(favicon(__dirname + '/public/img/favicon.ico')); | |||
@@ -0,0 +1,11 @@ | |||
users | |||
view | |||
edit | |||
ban | |||
file | |||
upload | |||
api | |||
create | |||
delete |