1
0
Fork 0

wip(backend): Added lowercase username field

This commit is contained in:
joostdecock 2022-11-02 12:33:32 +01:00
parent efe9e6c24d
commit f6a796959b
7 changed files with 122 additions and 91 deletions

View file

@ -7,6 +7,7 @@
*.png *.png
*.svg *.svg
*.prisma *.prisma
*.sqlite
yarn.lock yarn.lock
.husky/pre-commit .husky/pre-commit
.prettierignore .prettierignore

View file

@ -41,7 +41,8 @@ model User {
role String @default("user") role String @default("user")
status Int @default(0) status Int @default(0)
updatedAt DateTime? @updatedAt updatedAt DateTime? @updatedAt
username String @unique username String
lusername String @unique
@@index([ihash]) @@index([ihash])
} }

Binary file not shown.

View file

@ -32,6 +32,7 @@ console.log(' 🕺 ', Object.keys(data.people).length, 'people')
console.log(' 👕 ', Object.keys(data.patterns).length, 'patterns') console.log(' 👕 ', Object.keys(data.patterns).length, 'patterns')
console.log(' 📰 ', data.subscribers.length, 'subscribers') console.log(' 📰 ', data.subscribers.length, 'subscribers')
console.log() console.log()
data.lusernames = {}
data.userhandles = {} data.userhandles = {}
await migrateUsers(data.users) await migrateUsers(data.users)
console.log() console.log()
@ -125,7 +126,9 @@ async function migrateUsers(users) {
async function createUser(user) { async function createUser(user) {
const ehash = hash(clean(user.email)) const ehash = hash(clean(user.email))
const record = await prisma.user.create({ let record
try {
record = await prisma.user.create({
data: { data: {
consent: user.consent, consent: user.consent,
createdAt: user.createdAt, createdAt: user.createdAt,
@ -143,9 +146,15 @@ async function createUser(user) {
role: user.role, role: user.role,
status: user.status, status: user.status,
username: user.username, username: user.username,
lusername: user.username.toLowerCase(),
}, },
}) })
} catch (err) {
console.log(user, err, data.lusernames[user.username.toLowerCase()])
process.exit()
}
data.userhandles[user.handle] = record.id data.userhandles[user.handle] = record.id
data.lusernames[record.lusername] = user
} }
/* /*

View file

@ -28,6 +28,7 @@ const asAccount = (user, decrypt) => ({
status: user.status, status: user.status,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
username: user.username, username: user.username,
lusername: user.lusername,
}) })
const getToken = (user, config) => const getToken = (user, config) =>
@ -72,6 +73,7 @@ UserController.prototype.signup = async (req, res, tools) => {
// It does not. Creating user entry // It does not. Creating user entry
let record let record
try { try {
const username = clean(randomString()) // Temporary username,
record = await prisma.user.create({ record = await prisma.user.create({
data: { data: {
data: asJson({ settings: { language: req.body.language } }), data: asJson({ settings: { language: req.body.language } }),
@ -80,7 +82,8 @@ UserController.prototype.signup = async (req, res, tools) => {
ihash: emailhash, ihash: emailhash,
initial: encrypt(clean(req.body.email)), initial: encrypt(clean(req.body.email)),
password: asJson(hashPassword(req.body.password)), password: asJson(hashPassword(req.body.password)),
username: randomString(), // Temporary username, username,
lusername: username.toLowerCase(),
}, },
}) })
} catch (err) { } catch (err) {
@ -94,6 +97,7 @@ UserController.prototype.signup = async (req, res, tools) => {
where: { id: record.id }, where: { id: record.id },
data: { data: {
username: `user-${record.id}`, username: `user-${record.id}`,
lusername: `user-${record.id}`,
}, },
}) })
} catch (err) { } catch (err) {
@ -229,47 +233,50 @@ UserController.prototype.confirm = async (req, res, tools) => {
} }
/* /*
UserController.prototype.login = function (req, res, prisma, config) { * Login (with username and password)
if (!req.body || !req.body.username) return res.sendStatus(400) *
User.findOne( * This is the endpoint that provides traditional username/password login
{ * See: https://freesewing.dev/reference/backend/api
$or: [ */
{ username: req.body.username.toLowerCase().trim() }, UserController.prototype.login = async function (req, res, tools) {
{ username: req.body.username.trim() }, if (Object.keys(req.body) < 1) return res.status(400).json({ error: 'postBodyMissing', result })
{ ehash: ehash(req.body.username) }, if (!req.body.username) return res.status(400).json({ error: 'usernameMissing', result })
if (!req.body.password) return res.status(400).json({ error: 'passwordMissing', result })
// Destructure what we need from tools
const { prisma, config, decrypt } = tools
// Retrieve user account
let account
try {
account = await prisma.user.findFirst({
where: {
OR: [
{ lusername: { equals: clean(req.body.username) } },
{ ehash: { equals: hash(clean(req.body.username)) } },
{ id: { equals: parseInt(req.body.username) || -1 } },
], ],
}, },
(err, user) => {
if (err) return res.sendStatus(400)
if (user === null) return res.sendStatus(401)
user.verifyPassword(req.body.password, (err, valid) => {
if (err) return res.sendStatus(400)
else if (valid) {
if (user.status !== 'active') return res.sendStatus(403)
else {
log.info('login', { user, req })
let account = user.account()
let token = getToken(account)
let people = {}
Person.find({ user: user.handle }, (err, personList) => {
if (err) return res.sendStatus(400)
for (let person of personList) people[person.handle] = person.info()
let patterns = {}
Pattern.find({ user: user.handle }, (err, patternList) => {
if (err) return res.sendStatus(400)
for (let pattern of patternList) patterns[pattern.handle] = pattern
return user.updateLoginTime(() => res.send({ account, people, patterns, token }))
}) })
} catch (err) {
log.warn(err, `Error while trying to find username: ${req.body.username}`)
return res.status(401).send({ error: 'logingFailed', result })
}
if (!account) {
log.warn(`Login attempt for non-existing user: ${req.body.username} from ${req.ip}`)
return res.status(401).send({ error: 'logingFailed', result })
}
// Login success
log.info(`Login by user ${account.id} (${account.username})`)
return res.status(200).send({
result: 'success',
token: getToken(account, config),
account: asAccount({ ...account }, decrypt),
}) })
} }
} else { /*
log.warning('wrongPassword', { user, req })
return res.sendStatus(401)
}
})
}
)
}
// For people who have forgotten their password, or password-less logins // For people who have forgotten their password, or password-less logins
UserController.prototype.confirmationLogin = function (req, res) { UserController.prototype.confirmationLogin = function (req, res) {

View file

@ -3,20 +3,16 @@ import { UserController } from '../controllers/user.mjs'
const User = new UserController() const User = new UserController()
const jwt = ['jwt', { session: false }] const jwt = ['jwt', { session: false }]
export function userRoutes(tools) { export function userRoutes(tools) {
const { app, passport } = tools const { app, passport } = tools
// Sign up // Sign up
app.post( app.post('/signup', (req, res) => User.signup(req, res, tools))
'/signup',
(req, res) => User.signup(req, res, tools)
)
// Confirm account // Confirm account
app.post( app.post('/confirm/signup/:id', (req, res) => User.confirm(req, res, tools))
'/confirm/signup/:id',
(req, res) => User.confirm(req, res, tools) // Login
) app.post('/login', (req, res) => User.login(req, res, tools))
// Create account // Create account
//app.post( //app.post(
@ -79,11 +75,6 @@ export function userRoutes(tools) {
(req, res) => User.resend(req, res, params) (req, res) => User.resend(req, res, params)
) )
// Login
app.post(
'/login',
(req, res) => User.login(req, res, params)
)
// Get list of patrons // Get list of patrons
app.get( app.get(
@ -105,4 +96,3 @@ export function userRoutes(tools) {
) )
*/ */
} }

View file

@ -6,12 +6,17 @@ import { randomString } from '../src/utils/crypto.mjs'
const config = verifyConfig() const config = verifyConfig()
const expect = chai.expect const expect = chai.expect
chai.use(http) chai.use(http)
const user = '🧑'
const email = `test_${randomString()}@mailtrap.freesewing.dev`
const store = {} const store = {}
const data = {
email: `test_${randomString()}@mailtrap.freesewing.dev`,
language: 'en',
password: 'One two one two, this is just a test',
}
describe('Non language-specific User controller signup routes', () => { describe(`${user} Signup flow and authentication`, () => {
it('Should return 400 on signup without body', (done) => { it(`${user} Should return 400 on signup without body`, (done) => {
chai chai
.request(config.api) .request(config.api)
.post('/signup') .post('/signup')
@ -25,14 +30,8 @@ describe('Non language-specific User controller signup routes', () => {
}) })
}) })
const data = {
email,
language: 'en',
password: 'One two one two, this is just a test',
}
Object.keys(data).map((key) => { Object.keys(data).map((key) => {
it(`Should not create signup without ${key}`, (done) => { it(`${user} Should not allow signup without ${key}`, (done) => {
chai chai
.request(config.api) .request(config.api)
.post('/signup') .post('/signup')
@ -55,7 +54,7 @@ describe('Non language-specific User controller signup routes', () => {
}) })
}) })
step(`should signup a new user (${email})`, (done) => { step(`${user} Should signup new user ${data.email}`, (done) => {
chai chai
.request(config.api) .request(config.api)
.post('/signup') .post('/signup')
@ -68,13 +67,13 @@ describe('Non language-specific User controller signup routes', () => {
expect(res.type).to.equal('application/json') expect(res.type).to.equal('application/json')
expect(res.charset).to.equal('utf-8') expect(res.charset).to.equal('utf-8')
expect(res.body.result).to.equal(`success`) expect(res.body.result).to.equal(`success`)
expect(res.body.email).to.equal(email) expect(res.body.email).to.equal(data.email)
store.confirmation = res.body.confirmation store.confirmation = res.body.confirmation
done() done()
}) })
}) })
step(`should confirm a new user (${email})`, (done) => { step(`${user} Should confirm new user (${data.email})`, (done) => {
chai chai
.request(config.api) .request(config.api)
.post(`/confirm/signup/${store.confirmation}`) .post(`/confirm/signup/${store.confirmation}`)
@ -87,11 +86,12 @@ describe('Non language-specific User controller signup routes', () => {
expect(typeof res.body.token).to.equal(`string`) expect(typeof res.body.token).to.equal(`string`)
expect(typeof res.body.account.id).to.equal(`number`) expect(typeof res.body.account.id).to.equal(`number`)
store.token = res.body.token store.token = res.body.token
store.username = res.body.account.username
done() done()
}) })
}) })
step('should fail to signup an existing email address', (done) => { step(`${user} Should fail to signup an existing email address`, (done) => {
chai chai
.request(config.api) .request(config.api)
.post('/signup') .post('/signup')
@ -106,6 +106,29 @@ describe('Non language-specific User controller signup routes', () => {
}) })
}) })
step(`${user} Should login with username and password`, (done) => {
chai
.request(config.api)
.post('/login')
.send({
username: store.username,
password: data.email,
})
.end((err, res) => {
expect(res.status).to.equal(200)
expect(res.type).to.equal('application/json')
expect(res.charset).to.equal('utf-8')
expect(res.body.result).to.equal(`success`)
expect(res.body.account.email).to.equal(data.email)
expect(res.body.account.username).to.equal(store.username)
expect(res.body.account.lusername).to.equal(store.username.toLowerCase())
expect(typeof res.body.token).to.equal(`string`)
expect(typeof res.body.account.id).to.equal(`number`)
store.token = res.body.token
done()
})
})
/* /*