aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--app/Controllers/Http/DashboardController.js155
-rw-r--r--app/Controllers/Http/UserController.js11
-rw-r--r--app/Exceptions/Handler.js2
-rw-r--r--config/session.js99
-rw-r--r--config/shield.js145
-rw-r--r--package-lock.json91
-rw-r--r--package.json2
-rw-r--r--public/css/main.css69
-rw-r--r--resources/views/dashboard/account.edge63
-rw-r--r--resources/views/dashboard/data.edge167
-rw-r--r--resources/views/dashboard/delete.edge31
-rw-r--r--resources/views/dashboard/login.edge42
-rw-r--r--resources/views/layouts/main.edge18
-rw-r--r--start/app.js3
-rw-r--r--start/kernel.js6
-rw-r--r--start/routes.js23
17 files changed, 925 insertions, 6 deletions
diff --git a/README.md b/README.md
index b686fad..0c7bbd9 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
1# ferdi-server 1# ferdi-server
2Unofficial Franz server replacement for use with the Ferdi Client. 2Unofficial Franz server replacement for use with the Ferdi Client.
3 3
4## Why use a custom ferdi-server?
5A custom ferdi-server allows you to experience the full potential of the Ferdi client.
6
7
4## Features 8## Features
5- [x] User registration and login 9- [x] User registration and login
6- [x] Service creation, download, listing and removing 10- [x] Service creation, download, listing and removing
diff --git a/app/Controllers/Http/DashboardController.js b/app/Controllers/Http/DashboardController.js
new file mode 100644
index 0000000..aa8127f
--- /dev/null
+++ b/app/Controllers/Http/DashboardController.js
@@ -0,0 +1,155 @@
1'use strict'
2
3const {
4 validateAll
5} = use('Validator');
6
7const crypto = require('crypto');
8
9class DashboardController {
10 async login({
11 request,
12 response,
13 auth,
14 session
15 }) {
16 const validation = await validateAll(request.all(), {
17 mail: 'required|email',
18 password: 'required',
19 });
20 if (validation.fails()) {
21 session.withErrors({
22 type: 'danger',
23 message: 'Invalid mail or password'
24 }).flashExcept(['password']);
25 return response.redirect('back');
26 }
27
28 let {
29 mail,
30 password
31 } = request.all()
32
33 const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');
34
35 try {
36 await auth.authenticator('session').attempt(mail, hashedPassword)
37 } catch (error) {
38 session.flash({
39 type: 'danger',
40 message: 'Invalid mail or password'
41 })
42 return response.redirect('back');
43 }
44 return response.redirect('/user/account');
45 }
46
47 async account({
48 auth,
49 view
50 }) {
51 try {
52 await auth.check()
53 } catch (error) {
54 return response.redirect('/user/login');
55 }
56
57 return view.render('dashboard.account', {
58 username: auth.user.username,
59 email: auth.user.email
60 });
61 }
62
63 async edit({
64 auth,
65 request,
66 session,
67 view,
68 response
69 }) {
70 let validation = await validateAll(request.all(), {
71 username: 'required',
72 email: 'required'
73 });
74 if (validation.fails()) {
75 session.withErrors(validation.messages()).flashExcept(['password']);
76 return response.redirect('back');
77 }
78
79 // Check new username
80 if (request.input('username') !== auth.user.username) {
81 validation = await validateAll(request.all(), {
82 username: 'required|unique:users,username',
83 email: 'required'
84 });
85 if (validation.fails()) {
86 session.withErrors(validation.messages()).flashExcept(['password']);
87 return response.redirect('back');
88 }
89 }
90
91 // Check new email
92 if (request.input('email') !== auth.user.email) {
93 validation = await validateAll(request.all(), {
94 username: 'required',
95 email: 'required|email|unique:users,email'
96 });
97 if (validation.fails()) {
98 session.withErrors(validation.messages()).flashExcept(['password']);
99 return response.redirect('back');
100 }
101 }
102
103 // Update user account
104 auth.user.username = request.input('username');
105 auth.user.email = request.input('email');
106 if (!!request.input('password')) {
107 const hashedPassword = crypto.createHash('sha256').update(request.input('password')).digest('base64');
108 auth.user.password = hashedPassword;
109 }
110 auth.user.save();
111
112 return view.render('dashboard.account', {
113 username: auth.user.username,
114 email: auth.user.email,
115 success: true
116 });
117 }
118
119 async data({
120 auth,
121 view
122 }) {
123 const general = auth.user;
124 const services = (await auth.user.services().fetch()).toJSON();
125 const workspaces = (await auth.user.workspaces().fetch()).toJSON();
126
127 return view.render('dashboard.data', {
128 username: general.username,
129 mail: general.email,
130 created: general.created_at,
131 updated: general.updated_at,
132 services,
133 workspaces,
134 });
135 }
136
137 logout({
138 auth,
139 response
140 }) {
141 auth.authenticator('session').logout();
142 return response.redirect('/user/login');
143 }
144
145 delete({
146 auth,
147 response
148 }) {
149 auth.user.delete();
150 auth.authenticator('session').logout();
151 return response.redirect('/user/login');
152 }
153}
154
155module.exports = DashboardController
diff --git a/app/Controllers/Http/UserController.js b/app/Controllers/Http/UserController.js
index 2a75f6e..ced27bb 100644
--- a/app/Controllers/Http/UserController.js
+++ b/app/Controllers/Http/UserController.js
@@ -192,9 +192,9 @@ class UserController {
192 192
193 if(Env.get('CONNECT_WITH_FRANZ') == 'false') { 193 if(Env.get('CONNECT_WITH_FRANZ') == 'false') {
194 await User.create({ 194 await User.create({
195 email: userInf.email, 195 email,
196 password: hashedPassword, 196 password: hashedPassword,
197 username: userInf.firstname 197 username: 'Franz'
198 }); 198 });
199 199
200 return response.send('Your account has been created but due to this server\'s configuration, we could not import your Franz account data.\n\nIf you are the server owner, please set CONNECT_WITH_FRANZ to true to enable account imports.') 200 return response.send('Your account has been created but due to this server\'s configuration, we could not import your Franz account data.\n\nIf you are the server owner, please set CONNECT_WITH_FRANZ to true to enable account imports.')
@@ -231,13 +231,18 @@ class UserController {
231 } 231 }
232 232
233 // Get user information 233 // Get user information
234 let userInf; 234 let userInf = false;
235 try { 235 try {
236 userInf = await franzRequest('me', 'GET', token) 236 userInf = await franzRequest('me', 'GET', token)
237 console.log('A', userInf)
237 } catch (e) { 238 } catch (e) {
238 const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later.\nError: ' + e; 239 const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later.\nError: ' + e;
239 return response.status(401).send(errorMessage) 240 return response.status(401).send(errorMessage)
240 } 241 }
242 if (!userInf) {
243 const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later.\nError: ' + e;
244 return response.status(401).send(errorMessage)
245 }
241 246
242 // Create user in DB 247 // Create user in DB
243 let user; 248 let user;
diff --git a/app/Exceptions/Handler.js b/app/Exceptions/Handler.js
index 94d7246..efa2e0b 100644
--- a/app/Exceptions/Handler.js
+++ b/app/Exceptions/Handler.js
@@ -23,6 +23,8 @@ class ExceptionHandler extends BaseExceptionHandler {
23 async handle (error, { request, response }) { 23 async handle (error, { request, response }) {
24 if (error.name === 'ValidationException') { 24 if (error.name === 'ValidationException') {
25 return response.status(400).send('Invalid arguments') 25 return response.status(400).send('Invalid arguments')
26 } else if (error.name === 'InvalidSessionException') {
27 return response.status(401).redirect('/user/login');
26 } 28 }
27 29
28 response.status(error.status).send(error.message) 30 response.status(error.status).send(error.message)
diff --git a/config/session.js b/config/session.js
new file mode 100644
index 0000000..f49b9b7
--- /dev/null
+++ b/config/session.js
@@ -0,0 +1,99 @@
1'use strict'
2
3const Env = use('Env')
4
5module.exports = {
6 /*
7 |--------------------------------------------------------------------------
8 | Session Driver
9 |--------------------------------------------------------------------------
10 |
11 | The session driver to be used for storing session values. It can be
12 | cookie, file or redis.
13 |
14 | For `redis` driver, make sure to install and register `@adonisjs/redis`
15 |
16 */
17 driver: Env.get('SESSION_DRIVER', 'cookie'),
18
19 /*
20 |--------------------------------------------------------------------------
21 | Cookie Name
22 |--------------------------------------------------------------------------
23 |
24 | The name of the cookie to be used for saving session id. Session ids
25 | are signed and encrypted.
26 |
27 */
28 cookieName: 'adonis-session',
29
30 /*
31 |--------------------------------------------------------------------------
32 | Clear session when browser closes
33 |--------------------------------------------------------------------------
34 |
35 | If this value is true, the session cookie will be temporary and will be
36 | removed when browser closes.
37 |
38 */
39 clearWithBrowser: true,
40
41 /*
42 |--------------------------------------------------------------------------
43 | Session age
44 |--------------------------------------------------------------------------
45 |
46 | This value is only used when `clearWithBrowser` is set to false. The
47 | age must be a valid https://npmjs.org/package/ms string or should
48 | be in milliseconds.
49 |
50 | Valid values are:
51 | '2h', '10d', '5y', '2.5 hrs'
52 |
53 */
54 age: '2h',
55
56 /*
57 |--------------------------------------------------------------------------
58 | Cookie options
59 |--------------------------------------------------------------------------
60 |
61 | Cookie options defines the options to be used for setting up session
62 | cookie
63 |
64 */
65 cookie: {
66 httpOnly: true,
67 path: '/',
68 sameSite: false
69 },
70
71 /*
72 |--------------------------------------------------------------------------
73 | Sessions location
74 |--------------------------------------------------------------------------
75 |
76 | If driver is set to file, we need to define the relative location from
77 | the temporary path or absolute url to any location.
78 |
79 */
80 file: {
81 location: 'sessions'
82 },
83
84 /*
85 |--------------------------------------------------------------------------
86 | Redis config
87 |--------------------------------------------------------------------------
88 |
89 | The configuration for the redis driver.
90 |
91 */
92 redis: {
93 host: '127.0.0.1',
94 port: 6379,
95 password: null,
96 db: 0,
97 keyPrefix: ''
98 }
99}
diff --git a/config/shield.js b/config/shield.js
new file mode 100644
index 0000000..3d4526a
--- /dev/null
+++ b/config/shield.js
@@ -0,0 +1,145 @@
1'use strict'
2
3module.exports = {
4 /*
5 |--------------------------------------------------------------------------
6 | Content Security Policy
7 |--------------------------------------------------------------------------
8 |
9 | Content security policy filters out the origins not allowed to execute
10 | and load resources like scripts, styles and fonts. There are wide
11 | variety of options to choose from.
12 */
13 csp: {
14 /*
15 |--------------------------------------------------------------------------
16 | Directives
17 |--------------------------------------------------------------------------
18 |
19 | All directives are defined in camelCase and here is the list of
20 | available directives and their possible values.
21 |
22 | https://content-security-policy.com
23 |
24 | @example
25 | directives: {
26 | defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com']
27 | }
28 |
29 */
30 directives: {
31 },
32 /*
33 |--------------------------------------------------------------------------
34 | Report only
35 |--------------------------------------------------------------------------
36 |
37 | Setting `reportOnly=true` will not block the scripts from running and
38 | instead report them to a URL.
39 |
40 */
41 reportOnly: false,
42 /*
43 |--------------------------------------------------------------------------
44 | Set all headers
45 |--------------------------------------------------------------------------
46 |
47 | Headers staring with `X` have been depreciated, since all major browsers
48 | supports the standard CSP header. So its better to disable deperciated
49 | headers, unless you want them to be set.
50 |
51 */
52 setAllHeaders: false,
53
54 /*
55 |--------------------------------------------------------------------------
56 | Disable on android
57 |--------------------------------------------------------------------------
58 |
59 | Certain versions of android are buggy with CSP policy. So you can set
60 | this value to true, to disable it for Android versions with buggy
61 | behavior.
62 |
63 | Here is an issue reported on a different package, but helpful to read
64 | if you want to know the behavior. https://github.com/helmetjs/helmet/pull/82
65 |
66 */
67 disableAndroid: true
68 },
69
70 /*
71 |--------------------------------------------------------------------------
72 | X-XSS-Protection
73 |--------------------------------------------------------------------------
74 |
75 | X-XSS Protection saves from applications from XSS attacks. It is adopted
76 | by IE and later followed by some other browsers.
77 |
78 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
79 |
80 */
81 xss: {
82 enabled: true,
83 enableOnOldIE: false
84 },
85
86 /*
87 |--------------------------------------------------------------------------
88 | Iframe Options
89 |--------------------------------------------------------------------------
90 |
91 | xframe defines whether or not your website can be embedded inside an
92 | iframe. Choose from one of the following options.
93 | @available options
94 | DENY, SAMEORIGIN, ALLOW-FROM http://example.com
95 |
96 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
97 */
98 xframe: 'DENY',
99
100 /*
101 |--------------------------------------------------------------------------
102 | No Sniff
103 |--------------------------------------------------------------------------
104 |
105 | Browsers have a habit of sniffing content-type of a response. Which means
106 | files with .txt extension containing Javascript code will be executed as
107 | Javascript. You can disable this behavior by setting nosniff to false.
108 |
109 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
110 |
111 */
112 nosniff: true,
113
114 /*
115 |--------------------------------------------------------------------------
116 | No Open
117 |--------------------------------------------------------------------------
118 |
119 | IE users can execute webpages in the context of your website, which is
120 | a serious security risk. Below option will manage this for you.
121 |
122 */
123 noopen: true,
124
125 /*
126 |--------------------------------------------------------------------------
127 | CSRF Protection
128 |--------------------------------------------------------------------------
129 |
130 | CSRF Protection adds another layer of security by making sure, actionable
131 | routes does have a valid token to execute an action.
132 |
133 */
134 csrf: {
135 enable: true,
136 methods: ['POST', 'PUT', 'DELETE'],
137 filterUris: [],
138 cookieOptions: {
139 httpOnly: false,
140 sameSite: true,
141 path: '/',
142 maxAge: 7200
143 }
144 }
145}
diff --git a/package-lock.json b/package-lock.json
index dfc1308..0a8707b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -299,6 +299,49 @@
299 } 299 }
300 } 300 }
301 }, 301 },
302 "@adonisjs/session": {
303 "version": "1.0.29",
304 "resolved": "https://registry.npmjs.org/@adonisjs/session/-/session-1.0.29.tgz",
305 "integrity": "sha512-dr8P/WUrt50uLZt5SZ8C1uhp4FXzGPh1dNUeS0Lur95ftzHri6S+suBXQ4DM+sNiZQn7iaVylYwsFqUlOkp8gQ==",
306 "requires": {
307 "@adonisjs/generic-exceptions": "^2.0.1",
308 "bson": "^1.1.0",
309 "debug": "^4.1.0",
310 "fs-extra": "^7.0.0",
311 "lodash": "^4.17.11",
312 "ms": "^2.1.1",
313 "type-of-is": "^3.5.1",
314 "uuid": "^3.3.2"
315 },
316 "dependencies": {
317 "debug": {
318 "version": "4.1.1",
319 "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
320 "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
321 "requires": {
322 "ms": "^2.1.1"
323 }
324 },
325 "ms": {
326 "version": "2.1.2",
327 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
328 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
329 }
330 }
331 },
332 "@adonisjs/shield": {
333 "version": "1.0.8",
334 "resolved": "https://registry.npmjs.org/@adonisjs/shield/-/shield-1.0.8.tgz",
335 "integrity": "sha512-bFPuDIlChrp7ihbwjl8OVCw/b57pG0yn/1rrzFXP2XBpIUTbOFdQxzDlvydHxsyg90/pAup/4qtmdulywczg2g==",
336 "requires": {
337 "@adonisjs/generic-exceptions": "^2.0.1",
338 "csrf": "^3.0.6",
339 "node-cookie": "^2.1.1",
340 "node-csp": "^1.0.1",
341 "node-guard": "^1.0.0",
342 "uuid": "^3.3.2"
343 }
344 },
302 "@adonisjs/validator": { 345 "@adonisjs/validator": {
303 "version": "5.0.6", 346 "version": "5.0.6",
304 "resolved": "https://registry.npmjs.org/@adonisjs/validator/-/validator-5.0.6.tgz", 347 "resolved": "https://registry.npmjs.org/@adonisjs/validator/-/validator-5.0.6.tgz",
@@ -836,6 +879,11 @@
836 "to-regex": "^3.0.1" 879 "to-regex": "^3.0.1"
837 } 880 }
838 }, 881 },
882 "bson": {
883 "version": "1.1.1",
884 "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz",
885 "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg=="
886 },
839 "btoa": { 887 "btoa": {
840 "version": "1.2.1", 888 "version": "1.2.1",
841 "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", 889 "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
@@ -1240,6 +1288,16 @@
1240 "which": "^1.2.9" 1288 "which": "^1.2.9"
1241 } 1289 }
1242 }, 1290 },
1291 "csrf": {
1292 "version": "3.1.0",
1293 "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
1294 "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
1295 "requires": {
1296 "rndm": "1.2.0",
1297 "tsscmp": "1.0.6",
1298 "uid-safe": "2.1.5"
1299 }
1300 },
1243 "dashdash": { 1301 "dashdash": {
1244 "version": "1.14.1", 1302 "version": "1.14.1",
1245 "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 1303 "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -2721,6 +2779,14 @@
2721 } 2779 }
2722 } 2780 }
2723 }, 2781 },
2782 "node-csp": {
2783 "version": "1.0.1",
2784 "resolved": "https://registry.npmjs.org/node-csp/-/node-csp-1.0.1.tgz",
2785 "integrity": "sha1-MF6yN3yY2oQVq7sDcW76HU6wznI=",
2786 "requires": {
2787 "platform": "^1.3.1"
2788 }
2789 },
2724 "node-exceptions": { 2790 "node-exceptions": {
2725 "version": "3.0.0", 2791 "version": "3.0.0",
2726 "resolved": "https://registry.npmjs.org/node-exceptions/-/node-exceptions-3.0.0.tgz", 2792 "resolved": "https://registry.npmjs.org/node-exceptions/-/node-exceptions-3.0.0.tgz",
@@ -2731,6 +2797,11 @@
2731 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", 2797 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
2732 "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" 2798 "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
2733 }, 2799 },
2800 "node-guard": {
2801 "version": "1.0.0",
2802 "resolved": "https://registry.npmjs.org/node-guard/-/node-guard-1.0.0.tgz",
2803 "integrity": "sha1-5FSb63kcOxyEJ1WlJztzvosICjQ="
2804 },
2734 "node-pre-gyp": { 2805 "node-pre-gyp": {
2735 "version": "0.11.0", 2806 "version": "0.11.0",
2736 "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", 2807 "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
@@ -3093,6 +3164,11 @@
3093 "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", 3164 "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
3094 "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" 3165 "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
3095 }, 3166 },
3167 "platform": {
3168 "version": "1.3.5",
3169 "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz",
3170 "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q=="
3171 },
3096 "pluralize": { 3172 "pluralize": {
3097 "version": "7.0.0", 3173 "version": "7.0.0",
3098 "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", 3174 "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
@@ -3778,6 +3854,11 @@
3778 "glob": "^7.1.3" 3854 "glob": "^7.1.3"
3779 } 3855 }
3780 }, 3856 },
3857 "rndm": {
3858 "version": "1.2.0",
3859 "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
3860 "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
3861 },
3781 "safe-buffer": { 3862 "safe-buffer": {
3782 "version": "5.2.0", 3863 "version": "5.2.0",
3783 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 3864 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
@@ -4543,6 +4624,11 @@
4543 "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", 4624 "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
4544 "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" 4625 "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
4545 }, 4626 },
4627 "tsscmp": {
4628 "version": "1.0.6",
4629 "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
4630 "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
4631 },
4546 "tunnel-agent": { 4632 "tunnel-agent": {
4547 "version": "0.6.0", 4633 "version": "0.6.0",
4548 "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 4634 "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -4565,6 +4651,11 @@
4565 "mime-types": "~2.1.24" 4651 "mime-types": "~2.1.24"
4566 } 4652 }
4567 }, 4653 },
4654 "type-of-is": {
4655 "version": "3.5.1",
4656 "resolved": "https://registry.npmjs.org/type-of-is/-/type-of-is-3.5.1.tgz",
4657 "integrity": "sha1-7sL8ibgo2/mQDrZBbu4w9P4PzTE="
4658 },
4568 "uid-safe": { 4659 "uid-safe": {
4569 "version": "2.1.5", 4660 "version": "2.1.5",
4570 "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 4661 "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
diff --git a/package.json b/package.json
index e63e7de..927c513 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,8 @@
25 "@adonisjs/framework": "^5.0.9", 25 "@adonisjs/framework": "^5.0.9",
26 "@adonisjs/ignitor": "^2.0.8", 26 "@adonisjs/ignitor": "^2.0.8",
27 "@adonisjs/lucid": "^6.1.3", 27 "@adonisjs/lucid": "^6.1.3",
28 "@adonisjs/session": "^1.0.29",
29 "@adonisjs/shield": "^1.0.8",
28 "@adonisjs/validator": "^5.0.6", 30 "@adonisjs/validator": "^5.0.6",
29 "atob": "^2.1.2", 31 "atob": "^2.1.2",
30 "btoa": "^1.2.1", 32 "btoa": "^1.2.1",
diff --git a/public/css/main.css b/public/css/main.css
new file mode 100644
index 0000000..a1c5653
--- /dev/null
+++ b/public/css/main.css
@@ -0,0 +1,69 @@
1input {
2 margin-bottom: 1rem;
3 width: 100%;
4 padding: 0.5rem;
5}
6
7button, .button {
8 display: flex;
9 overflow: hidden;
10 padding: 12px 12px;
11 cursor: pointer;
12 width: 100%;
13 -webkit-user-select: none;
14 -moz-user-select: none;
15 -ms-user-select: none;
16 user-select: none;
17 transition: all 150ms linear;
18 text-align: center;
19 white-space: nowrap;
20 text-decoration: none !important;
21 text-transform: none;
22 text-transform: capitalize;
23 color: #fff !important;
24 border: 0 none;
25 border-radius: 4px;
26 font-size: 13px;
27 font-weight: 500;
28 line-height: 1.3;
29 -webkit-appearance: none;
30 -moz-appearance: none;
31 appearance: none;
32 justify-content: center;
33 align-items: center;
34 flex: 0 0 160px;
35 box-shadow: 2px 5px 10px #e4e4e4;
36 color: #FFFFFF;
37 background: #161616;
38}
39
40#dropzone {
41 width: 100%;
42 height: 30vh;
43 background-color: #ebebeb;
44
45 display: flex;
46 align-items: center;
47 justify-content: center;
48 text-align: center;
49
50 cursor: pointer;
51}
52
53#dropzone p {
54 font-size: 0.85rem;
55}
56
57#files {
58 display: none;
59}
60
61.alert {
62 background-color: #e7a8a6;
63 padding: 0.8rem;
64 margin-bottom: 1rem;
65}
66
67td {
68 word-break: break-all;
69} \ No newline at end of file
diff --git a/resources/views/dashboard/account.edge b/resources/views/dashboard/account.edge
new file mode 100644
index 0000000..0361fa4
--- /dev/null
+++ b/resources/views/dashboard/account.edge
@@ -0,0 +1,63 @@
1@layout('layouts.main')
2
3@section('content')
4<h2>Your Ferdi account</h2>
5@if(flashMessage('error'))
6 <div class="alert">
7 {{ flashMessage('error') }}
8 </div>
9@endif
10@if(old('message'))
11 <div class="alert">
12 {{ old('message') }}
13 </div>
14@endif
15@if(flashMessage('notification'))
16 <div class="alert">
17 {{ flashMessage('notification.message') }}
18 </div>
19@endif
20@if(success === true)
21 <div class="alert" style="background-color:#28C76F;">
22 Sucessfully updated your user account
23 </div>
24@endif
25
26<form action="/user/account" method="POST">
27 {{ csrfField() }}
28 <div>
29 <label>Name</label>
30 <div>
31 <input type="text" value="{{ old('name', username) }}" placeholder="Name" name="username" required>
32 </div>
33 </div>
34 <div>
35 <label>E-Mail</label>
36 <div>
37 <input type="email" value="{{ old('email', email) }}" placeholder="E-Mail" name="email" required>
38 </div>
39 </div>
40 <div>
41 <label>Password</label>
42 <div>
43 <input type="password" placeholder="*********" name="password">
44 </div>
45 </div>
46 <div>
47 <button style="background-color:#28C76F;margin-bottom:1rem;">Change settings</button>
48 </div>
49</form>
50
51<div>
52 <a class="button" href="/user/delete" style="background-color:#F6416C;margin-bottom:1rem;">Delete my account</a>
53</div>
54<div>
55 <a class="button" href="/user/data" style="margin-bottom:1rem;">My account data</a>
56</div>
57<div>
58 <a class="button" href="/user/logout">Logout</a>
59</div>
60
61</div>
62
63@endsection \ No newline at end of file
diff --git a/resources/views/dashboard/data.edge b/resources/views/dashboard/data.edge
new file mode 100644
index 0000000..1935229
--- /dev/null
+++ b/resources/views/dashboard/data.edge
@@ -0,0 +1,167 @@
1@layout('layouts.main')
2
3@section('content')
4<h2>Your Ferdi account data</h2>
5@if(flashMessage('error'))
6 <div class="alert">
7 {{ flashMessage('error') }}
8 </div>
9@endif
10@if(old('message'))
11 <div class="alert">
12 {{ old('message') }}
13 </div>
14@endif
15
16<h4>General account data</h4>
17<table>
18 <tr>
19 <th>
20 Name
21 </th>
22 <th>
23 Value
24 </th>
25 </tr>
26 <tr>
27 <td>
28 E-Mail
29 </td>
30 <td>
31 {{ mail }}
32 </td>
33 </tr>
34 <tr>
35 <td>
36 Username
37 </td>
38 <td>
39 {{ username }}
40 </td>
41 </tr>
42 <tr>
43 <td>
44 Created account on
45 </td>
46 <td>
47 {{ created }}
48 </td>
49 </tr>
50 <tr>
51 <td>
52 Last account update on
53 </td>
54 <td>
55 {{ updated }}
56 </td>
57 </tr>
58</table>
59
60<h4>Your services</h4>
61<table>
62 <tr>
63 <th>
64 Service ID
65 </th>
66 <th>
67 Name
68 </th>
69 <th>
70 Recipe ID
71 </th>
72 <th>
73 Settings
74 </th>
75 <th>
76 Created
77 </th>
78 <th>
79 Last updated
80 </th>
81 </tr>
82
83 @each(service in services)
84 <tr>
85 <td>
86 {{ service.serviceId }}
87 </td>
88 <td>
89 {{ service.name }}
90 </td>
91 <td>
92 {{ service.recipeId }}
93 </td>
94 <td>
95 {{ service.settings }}
96 </td>
97 <td>
98 {{ service.created_at }}
99 </td>
100 <td>
101 {{ service.updated_at }}
102 </td>
103 </tr>
104 @endeach
105</table>
106
107
108<h4>Your workspaces</h4>
109<table>
110 <tr>
111 <th>
112 Service ID
113 </th>
114 <th>
115 Name
116 </th>
117 <th>
118 Order
119 </th>
120 <th>
121 Services
122 </th>
123 <th>
124 Data
125 </th>
126 <th>
127 Created
128 </th>
129 <th>
130 Last updated
131 </th>
132 </tr>
133
134 @each(workspace in workspaces)
135 <tr>
136 <td>
137 {{ workspace.workspaceId }}
138 </td>
139 <td>
140 {{ workspace.name }}
141 </td>
142 <td>
143 {{ workspace.order }}
144 </td>
145 <td>
146 {{ workspace.services }}
147 </td>
148 <td>
149 {{ workspace.data }}
150 </td>
151 <td>
152 {{ workspace.created_at }}
153 </td>
154 <td>
155 {{ workspace.updated_at }}
156 </td>
157 </tr>
158 @endeach
159</table>
160
161<div>
162 <a class="button" href="/user/account">Back to my account</a>
163</div>
164
165</div>
166
167@endsection \ No newline at end of file
diff --git a/resources/views/dashboard/delete.edge b/resources/views/dashboard/delete.edge
new file mode 100644
index 0000000..d3fc0df
--- /dev/null
+++ b/resources/views/dashboard/delete.edge
@@ -0,0 +1,31 @@
1@layout('layouts.main')
2
3@section('content')
4<h2>Delete Ferdi account</h2>
5@if(flashMessage('error'))
6 <div class="alert">
7 {{ flashMessage('error') }}
8 </div>
9@endif
10@if(old('message'))
11 <div class="alert">
12 {{ old('message') }}
13 </div>
14@endif
15
16<form action="/user/delete" method="POST">
17 <p>Are you sure, that you want to delete your Ferdi account?</p>
18 <p>This will <b>permanently</b> delete all your account settings, services and workspaces.</p>
19 <div>
20 {{ csrfField() }}
21 <button style="background-color:#F6416C;margin-bottom:1rem;">Yes, delete account</button>
22 </div>
23</form>
24
25<div>
26 <a class="button" href="/user/account" style="background-color:#28C76F;">Cancel</a>
27</div>
28
29</div>
30
31@endsection \ No newline at end of file
diff --git a/resources/views/dashboard/login.edge b/resources/views/dashboard/login.edge
new file mode 100644
index 0000000..58e97ba
--- /dev/null
+++ b/resources/views/dashboard/login.edge
@@ -0,0 +1,42 @@
1@layout('layouts.main')
2
3@section('content')
4<h2>Login</h2>
5@if(flashMessage('error'))
6 <div class="alert">
7 {{ flashMessage('error') }}
8 </div>
9@endif
10@if(old('message'))
11 <div class="alert">
12 {{ old('message') }}
13 </div>
14@endif
15<form action="/user/login" method="POST">
16 {{ csrfField() }}
17 <div>
18 <label>E-Mail</label>
19 <div>
20 <input type="email" value="{{ old('mail', '') }}" placeholder="E-Mail" name="mail">
21 </div>
22 </div>
23 <div>
24 <label>Password</label>
25 <div>
26 <input type="password" placeholder="*********" name="password">
27 </div>
28 </div>
29 <div>
30 <button>Login</button>
31 By logging in, you accept the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy policy</a>.
32
33 </div>
34 <div>
35 <p>Not a member yet? Register in the Ferdi app or <a href="../import">import your Franz account</a>.</p>
36 </div>
37</form>
38<br />
39
40</div>
41
42@endsection \ No newline at end of file
diff --git a/resources/views/layouts/main.edge b/resources/views/layouts/main.edge
new file mode 100644
index 0000000..b6cc8ca
--- /dev/null
+++ b/resources/views/layouts/main.edge
@@ -0,0 +1,18 @@
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>ferdi-server</title>
9
10 {{ style('css/vanilla') }}
11 {{ style('css/main') }}
12</head>
13
14<body>
15 @!section('content')
16</body>
17
18</html>
diff --git a/start/app.js b/start/app.js
index 0c32499..9cf2735 100644
--- a/start/app.js
+++ b/start/app.js
@@ -18,6 +18,9 @@ const providers = [
18 '@adonisjs/lucid/providers/LucidProvider', 18 '@adonisjs/lucid/providers/LucidProvider',
19 '@adonisjs/drive/providers/DriveProvider', 19 '@adonisjs/drive/providers/DriveProvider',
20 '@adonisjs/validator/providers/ValidatorProvider', 20 '@adonisjs/validator/providers/ValidatorProvider',
21 '@adonisjs/framework/providers/ViewProvider',
22 '@adonisjs/session/providers/SessionProvider',
23 '@adonisjs/shield/providers/ShieldProvider',
21] 24]
22 25
23/* 26/*
diff --git a/start/kernel.js b/start/kernel.js
index 3c2d26d..18fb5bf 100644
--- a/start/kernel.js
+++ b/start/kernel.js
@@ -15,7 +15,8 @@ const Server = use('Server')
15const globalMiddleware = [ 15const globalMiddleware = [
16 'Adonis/Middleware/BodyParser', 16 'Adonis/Middleware/BodyParser',
17 'App/Middleware/ConvertEmptyStringsToNull', 17 'App/Middleware/ConvertEmptyStringsToNull',
18 'Adonis/Middleware/AuthInit' 18 'Adonis/Middleware/AuthInit',
19 'Adonis/Middleware/Session',
19] 20]
20 21
21/* 22/*
@@ -37,7 +38,8 @@ const globalMiddleware = [
37*/ 38*/
38const namedMiddleware = { 39const namedMiddleware = {
39 auth: 'Adonis/Middleware/Auth', 40 auth: 'Adonis/Middleware/Auth',
40 guest: 'Adonis/Middleware/AllowGuestOnly' 41 guest: 'Adonis/Middleware/AllowGuestOnly',
42 shield: 'Adonis/Middleware/Shield',
41} 43}
42 44
43/* 45/*
diff --git a/start/routes.js b/start/routes.js
index 52f26b9..364eff9 100644
--- a/start/routes.js
+++ b/start/routes.js
@@ -60,7 +60,26 @@ Route.group(() => {
60 Route.get('announcements/:version', 'StaticController.announcement') 60 Route.get('announcements/:version', 'StaticController.announcement')
61}).prefix('v1') 61}).prefix('v1')
62 62
63// Dashboard 63// User dashboard
64Route.group(() => {
65 // Auth
66 Route.get('login', ({view}) => {
67 return view.render('dashboard.login');
68 }).middleware('guest');
69 Route.post('login', 'DashboardController.login').middleware('guest')
70
71 // Dashboard
72 Route.get('account', 'DashboardController.account').middleware('auth:session')
73 Route.post('account', 'DashboardController.edit').middleware('auth:session')
74 Route.get('data', 'DashboardController.data').middleware('auth:session')
75 Route.get('delete', ({view}) => {
76 return view.render('dashboard.delete');
77 }).middleware('auth:session');
78 Route.post('delete', 'DashboardController.delete').middleware('auth:session')
79 Route.get('logout', 'DashboardController.logout').middleware('auth:session')
80}).prefix('user').middleware('shield')
81
82// Recipe creation
64Route.post('new', 'RecipeController.create') 83Route.post('new', 'RecipeController.create')
65Route.get('new', ({ response }) => { 84Route.get('new', ({ response }) => {
66 if (Env.get('IS_CREATION_ENABLED') == 'false') { 85 if (Env.get('IS_CREATION_ENABLED') == 'false') {
@@ -69,6 +88,8 @@ Route.get('new', ({ response }) => {
69 return response.redirect('/new.html') 88 return response.redirect('/new.html')
70 } 89 }
71}) 90})
91
92// Franz account import
72Route.post('import', 'UserController.import') 93Route.post('import', 'UserController.import')
73Route.get('import', ({ response }) => response.redirect('/import.html')) 94Route.get('import', ({ response }) => response.redirect('/import.html'))
74 95