1
0
mirror of https://github.com/Foltik/Shimapan synced 2025-02-25 16:50:35 -05:00

Merge pull request #11 from Foltik/mean

Mean
This commit is contained in:
Jack Foltz 2018-01-15 17:16:30 -05:00 committed by GitHub
commit d3bef194ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 686 additions and 165 deletions

View File

@ -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);

View File

@ -11,6 +11,7 @@ var UploadSchema = mongoose.Schema({
default: 0
},
uploader: String,
uploadKey: String,
date: Date,
file: Object
});

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
};
}]);

View File

@ -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();
}
});
};
}]);

View File

@ -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;
};
}]);

View File

@ -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;
});
};
}]);

View File

@ -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;

View File

@ -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

View File

@ -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';
});
};

View File

@ -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);
});
};
}]);

View File

@ -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);
});
};
}]);

View File

@ -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);
});
};
}]

View File

@ -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);
});
};
}]

View File

@ -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.";

View File

@ -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);
});
}
function useInvite(code, username) {
Invite.updateOne({code: code}, {recipient: username, used: new Date()}, function (err) {
if (err) throw err;
// 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);
});
}
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.'});
});
});
}
);
// 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) {
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;

78
app/routes/invites.js Normal file
View File

@ -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;

View File

@ -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.'});

View File

@ -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'));
});

View File

@ -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'));
});

View File

@ -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'));
});

View File

@ -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});

View File

@ -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 entry = {
name: genFileName(),
uploader: req.session.passport.user,
created: Date.now(),
file: req.file
};
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);
});
}
};
Upload.create(entry, function (err, next) {
if (err) {
next(err);
} else {
res.send({
function uploadFile(req, res, type, key) {
if (!req.file)
return res.status(400).json({'message': 'No file specified.'});
// 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;

33
app/routes/users.js Normal file
View File

@ -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;

View File

@ -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'));
});

BIN
public/img/edge.webm Normal file

Binary file not shown.

View File

@ -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)]);

View File

@ -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}">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

1
server.js Normal file → Executable file
View File

@ -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'));

11
test/permissions.txt Normal file
View File

@ -0,0 +1,11 @@
users
view
edit
ban
file
upload
api
create
delete