1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
/*
|--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
*/
const { timingSafeEqual } = require('crypto');
/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
const Route = use('Route');
const { API_VERSION } = require('../../environment-remote');
// Run latest database migration
const migrate = require('./migrate');
migrate();
async function validateToken(clientToken, response, next) {
const serverToken = process.env.FERDIUM_LOCAL_TOKEN;
const valid =
serverToken &&
clientToken &&
timingSafeEqual(
Buffer.from(clientToken, 'utf8'),
Buffer.from(serverToken, 'utf8'),
);
if (valid) {
await next();
return true;
}
return response.forbidden();
}
const OnlyAllowFerdium = async ({ request, response }, next) => {
const version = request.header('X-Franz-Version');
if (!version) {
return response.forbidden();
}
const clientToken = request.header('X-Ferdium-Local-Token');
return validateToken(clientToken, response, next);
};
const RequireTokenInQS = async ({ request, response }, next) => {
const clientToken = request.get().token;
return validateToken(clientToken, response, next);
};
const FERDIUM_LOCAL_TOKEN_COOKIE = 'ferdium-local-token';
const RequireAuthenticatedBrowser = async ({ request, response }, next) => {
const clientToken = request.cookie(FERDIUM_LOCAL_TOKEN_COOKIE);
return validateToken(clientToken, response, next);
};
// Health: Returning if all systems function correctly
Route.get('health', ({ response }) =>
response.send({
api: 'success',
db: 'success',
}),
).middleware(OnlyAllowFerdium);
// API is grouped under '/v1/' route
Route.group(() => {
// User authentification
Route.post('auth/signup', 'UserController.signup');
Route.post('auth/login', 'UserController.login');
// User info
Route.get('me', 'UserController.me');
Route.put('me', 'UserController.updateMe');
// Service info
Route.post('service', 'ServiceController.create');
Route.put('service/reorder', 'ServiceController.reorder');
Route.put('service/:id', 'ServiceController.edit');
Route.delete('service/:id', 'ServiceController.delete');
Route.get('me/services', 'ServiceController.list');
// Recipe store
Route.get('recipes', 'RecipeController.list');
Route.get('recipes/search', 'RecipeController.search');
Route.get('recipes/popular', 'RecipeController.popularRecipes');
Route.get('recipes/download/:recipe', 'RecipeController.download');
Route.post('recipes/update', 'RecipeController.update');
// Workspaces
Route.put('workspace/:id', 'WorkspaceController.edit');
Route.delete('workspace/:id', 'WorkspaceController.delete');
Route.post('workspace', 'WorkspaceController.create');
Route.get('workspace', 'WorkspaceController.list');
})
.prefix(API_VERSION)
.middleware(OnlyAllowFerdium);
Route.group(() => {
Route.get('icon/:id', 'ImageController.icon');
})
.prefix(API_VERSION)
.middleware(RequireTokenInQS);
Route.group(() => {
// Franz account import
Route.post('import', 'UserController.import');
Route.get('import', ({ view }) => view.render('import'));
// Account transfer
Route.get('export', 'UserController.export');
Route.post('transfer', 'UserController.importFerdium');
Route.get('transfer', ({ view }) => view.render('transfer'));
// Index
Route.get('/', ({ view }) => view.render('index'));
}).middleware(RequireAuthenticatedBrowser);
Route.get('token/:token', ({ params: { token }, response }) => {
if (validateToken(token)) {
response.cookie(FERDIUM_LOCAL_TOKEN_COOKIE, token, {
httpOnly: true,
sameSite: true,
path: '/',
});
return response.redirect('/');
}
return response.forbidden();
});
|