Skip to content

Commit f697b80

Browse files
committed
User registration; db models structure
1 parent c286782 commit f697b80

File tree

4 files changed

+289
-53
lines changed

4 files changed

+289
-53
lines changed

server/webserver/api.js

+51-51
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { MongoClient } = require('mongodb');
22
const _ = require('lodash');
33
const { ErrorForClient } = require('./types');
44
const { parseQuery } = require('./TextQueryParser');
5+
const models = require('./dbModels');
56

67

78
let dbClient;
@@ -72,26 +73,26 @@ const sourceTypes = {
7273
authPass: { type: 'string', name: 'auth_pass' },
7374
},
7475
};
75-
function parseClientSourceValues(values, fieldValues) {
76+
function parseClientSourceValuesToDbValues(values, fieldValues) {
7677
// TODO: Move this validation to a validation lib
7778
let toUpdate = {};
78-
for (let prop in values) {
79+
for (let prop in fieldValues) {
7980
let field = fieldValues[prop];
80-
if (!field) {
81-
continue;
82-
}
81+
let userVal = values[prop];
8382

84-
let val = values[prop];
83+
if (userVal === undefined && field.required) {
84+
throw new ErrorForClient(`Missing required property '${prop}'`, 'missing_property');
85+
}
8586

8687
if (
87-
field.type === 'string' && typeof val !== 'string' ||
88-
field.type === 'number' && typeof val !== 'number' ||
89-
field.type === 'boolean' && typeof val !== 'boolean'
88+
field.type === 'string' && typeof userVal !== 'string' ||
89+
field.type === 'number' && typeof userVal !== 'number' ||
90+
field.type === 'boolean' && typeof userVal !== 'boolean'
9091
) {
9192
throw new ErrorForClient(`Invalid type for property '${prop}'`, 'invalid_type');
9293
}
9394

94-
toUpdate[field.name] = val;
95+
toUpdate[field.name] = userVal;
9596
}
9697

9798
return toUpdate;
@@ -161,6 +162,28 @@ const apiv1 = {
161162
throw new ErrorForClient('Invalid login', 'bad_auth');
162163
}
163164
},
165+
async register(apiCtx, userInfo) {
166+
let existingUser = await dbUsersCol.findOne({name: userInfo.name});
167+
if (existingUser) {
168+
throw new ErrorForClient('Username is unavailable', 'username_unavailable');
169+
}
170+
171+
let newVals = parseClientSourceValuesToDbValues(userInfo, {
172+
name: { type: 'string', name: 'name', required: true },
173+
password: { type: 'string', name: 'password', required: true },
174+
});
175+
176+
let newUser = models.User({
177+
_id: generateId(),
178+
...newVals,
179+
});
180+
await dbUsersCol.insertOne(newUser);
181+
182+
return {
183+
id: newUser._id,
184+
name: newUser.name,
185+
};
186+
},
164187
},
165188
account: {
166189
_meta: {
@@ -206,7 +229,7 @@ const apiv1 = {
206229
throw new ErrorForClient('Unknown type of message source', 'unknown_source_type');
207230
}
208231

209-
let toUpdate = parseClientSourceValues(values, fieldValues);
232+
let toUpdate = parseClientSourceValuesToDbValues(values, fieldValues);
210233
// Prepend the db doc field to each property
211234
for (let prop in toUpdate) {
212235
toUpdate['sources.$.' + prop] = toUpdate[prop];
@@ -234,15 +257,16 @@ const apiv1 = {
234257
throw new ErrorForClient('Unknown type of message source', 'unknown_source_type');
235258
}
236259

237-
let newSource = parseClientSourceValues(values, fieldValues);
238-
if (Object.keys(newSource).length === 0) {
239-
throw new ErrorForClient('Missing name for the new source', 'missing_params');
260+
let newVals = parseClientSourceValuesToDbValues(values, fieldValues);
261+
if (Object.keys(newVals).length === 0) {
262+
throw new ErrorForClient('Missing parameters for the new source', 'missing_params');
240263
}
241264

242-
let newId = generateId();
243-
newSource._id = newId;
244-
newSource.name = name.trim();
245-
newSource.imapUid = '';
265+
let newSource = models.UserSource({
266+
_id: generateId(),
267+
name: name.trim(),
268+
...newVals,
269+
});
246270

247271
await dbUsersCol.updateOne(
248272
{
@@ -251,7 +275,7 @@ const apiv1 = {
251275
{$push: { sources: newSource } }
252276
);
253277

254-
return { id: newId };
278+
return { id: newSource._id };
255279
},
256280

257281
async delete(apiCtx, sourceId) {
@@ -313,22 +337,22 @@ const apiv1 = {
313337
let query = values.filter || '';
314338
let parsedQuery = parseQuery(query);
315339

316-
let newId = generateId();
340+
let newLabel = models.UserLabel({
341+
_id: generateId(),
342+
name: labelName,
343+
unread: 0,
344+
filter: { raw: query, parsed: parsedQuery }
345+
});
317346

318347
await dbUsersCol.updateOne(
319348
{_id: apiCtx.session.uid},
320349
{
321350
$addToSet: {
322-
labels: {
323-
_id: newId,
324-
name: labelName,
325-
unread: 0,
326-
filter: { raw: query, parsed: parsedQuery }
327-
},
351+
labels: newLabel,
328352
},
329353
}
330354
);
331-
return {id: newId, name: labelName};
355+
return {id: newLabel._id, name: labelName};
332356
},
333357
delete: async(apiCtx, labelId) => {
334358
await dbUsersCol.updateOne(
@@ -344,30 +368,6 @@ const apiv1 = {
344368
_meta: {
345369
runBefore: [requireAuth],
346370
},
347-
// TODO: should be "system" user only
348-
ingest: async (apiCtx, incoming) => {
349-
// TODO: validate the incoming message before inserting
350-
let message = {
351-
id: incoming.id,
352-
threadId: incoming.threadId || '',
353-
from: incoming.from, // 'some name <[email protected]>'
354-
to: incoming.to, //['[email protected]'],
355-
cc: incoming.cc, //['[email protected]'],
356-
bcc: incoming.bcc, //['[email protected]'],
357-
subject: incoming.subject, //'RE: RE: FW: help pls',
358-
bodyText: incoming.bodyText, //'wooo my body ' + i,
359-
bodyHtml: incoming.bodyHtml, //'wooo my <b>body</b> ' + i,
360-
labels: [
361-
1,
362-
],
363-
recieved: incoming.recieved ? incoming.recieved : Date.now(),
364-
read: incoming.read ? incoming.read : 0,
365-
inReplyTo: incoming.inReplyTo || '',
366-
raw: incoming.raw || '',
367-
};
368-
insertMessage(message);
369-
370-
},
371371
// Get the latest threads, and all messages within each thread. size=the number of threads
372372
latest: async (apiCtx, labelIds, size=100) => {
373373
let filter = {};

server/webserver/dbModels.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const _ = require('lodash');
2+
3+
module.exports.User = function User(source={}) {
4+
let template = {
5+
_id: '',
6+
name: '',
7+
password: '',
8+
sources: [ /* UserSource */ ],
9+
labels: [ /* UserLabel */ ],
10+
policies: {},
11+
sessions: [],
12+
};
13+
14+
return _.merge(template, source);
15+
}
16+
17+
module.exports.UserSource = function UserSource(source={}) {
18+
let template = {
19+
_id: '',
20+
name: '',
21+
type: '',
22+
ingestPing: 0,
23+
host: '',
24+
port: 993,
25+
tls: true,
26+
auth_user: '',
27+
auth_pass: '',
28+
imapUid: '',
29+
};
30+
31+
return _.merge(template, source);
32+
}
33+
34+
module.exports.UserLabel = function UserLabel(source={}) {
35+
let template = {
36+
_id: '',
37+
name: '',
38+
filter: {
39+
raw: '',
40+
parsed: {},
41+
},
42+
};
43+
44+
return _.merge(template, source);
45+
}
46+
47+
module.exports.UserSession = function UserSession(source={}) {
48+
let template = {
49+
_id: '',
50+
agent: '',
51+
agentVersion: '',
52+
// if country+fingerprint changes, logout
53+
agentFingerprint: '',
54+
country: '',
55+
seen: 0,
56+
};
57+
58+
return _.merge(template, source);
59+
}
60+
61+
module.exports.Thread = function Thread(source={}) {
62+
let template = {
63+
_id: '',
64+
accountId: '',
65+
sourceId: '',
66+
lastRecieved: 0,
67+
messages: [ /* ThreadMessage */ ],
68+
};
69+
70+
return _.merge(template, source);
71+
}
72+
73+
module.exports.ThreadMessage = function ThreadMessage(source={}) {
74+
let template = {
75+
_id: '',
76+
messageId: '',
77+
threadId: '',
78+
subject: '',
79+
// https://nodemailer.com/extras/mailparser/#address-object
80+
from: [],
81+
to: [],
82+
cc: [],
83+
bcc: [],
84+
bodyText: '',
85+
bodyHtml: '',
86+
labels: [ /* string */ ],
87+
recieved: 0,
88+
read: 0,
89+
inReplyTo: '',
90+
references: [],
91+
attachments: [ /* ThreadMessageAttachment */ ],
92+
// TODO: Fill this source with any attachment contents removed so its not bloated up
93+
source: '',
94+
sourceId: '',
95+
imapUid: '',
96+
};
97+
98+
return _.merge(template, source);
99+
}
100+
101+
module.exports.ThreadMessageAttachment = function ThreadMessageAttachment(source={}) {
102+
let template = {
103+
filename: '',
104+
type: '',
105+
cid: '',
106+
size: 0,
107+
};
108+
109+
return _.merge(template, source);
110+
}

src/components/AppLogin.vue

+26-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { useRoute, useRouter } from 'vue-router';
55
66
import AppInstance from '@/services/AppInstance';
77
import Logo from '@/components/Logo.vue';
8+
import RegisterUserVue from './registeruser/RegisterUser.vue';
9+
import RegisterUser from './registeruser/RegisterUser.vue';
810
911
const router = useRouter();
1012
const route = useRoute();
@@ -17,6 +19,8 @@ const loginForm = reactive({
1719
completed: false,
1820
});
1921
22+
const showRegisterUser = ref(false);
23+
2024
function sleep(l) {
2125
return new Promise(r => setTimeout(r, l));
2226
}
@@ -47,7 +51,14 @@ async function onLoginSubmit() {
4751
}
4852
}
4953
50-
if (AppInstance.instance().account.isLoggedIn()) {
54+
function onRegisteredComplete(event) {
55+
loginForm.username = event.name;
56+
loginForm.password = event.password;
57+
showRegisterUser.value = false;
58+
}
59+
60+
const appInstance = AppInstance.instance();
61+
if (appInstance.account.isLoggedIn()) {
5162
router.push({name: 'messages'});
5263
}
5364
@@ -59,7 +70,11 @@ if (AppInstance.instance().account.isLoggedIn()) {
5970
<div class="p-28 text-lg">
6071
<logo class="mb-12" />
6172

62-
<form @submit.prevent="onLoginSubmit" :class="{completed: loginForm.completed}">
73+
<form
74+
v-if="!showRegisterUser"
75+
@submit.prevent="onLoginSubmit"
76+
:class="{completed: loginForm.completed}"
77+
>
6378
<div class="slideaway overflow-hidden">
6479
<div v-if="loginForm.errorMessage" class="text-danger-800">
6580
{{loginForm.errorMessage}}
@@ -79,7 +94,16 @@ if (AppInstance.instance().account.isLoggedIn()) {
7994
<button type="submit" :disabled="loginForm.loading!==0 || !loginForm.username || !loginForm.password">
8095
{{loginForm.loading || loginForm.completed ? 'Signing in..' : 'Sign in'}}
8196
</button>
97+
98+
<div v-if="appInstance.policy('registration.enabled', true)" class="mt-16 text-base">
99+
<a @click="showRegisterUser=true">Create an account</a>
100+
</div>
82101
</form>
102+
<register-user
103+
v-else
104+
@close="showRegisterUser=false"
105+
@registered="onRegisteredComplete"
106+
></register-user>
83107
</div>
84108
</div>
85109
</template>

0 commit comments

Comments
 (0)