wip(backend): Added lowercase username field
This commit is contained in:
parent
efe9e6c24d
commit
f6a796959b
7 changed files with 122 additions and 91 deletions
|
@ -7,6 +7,7 @@
|
|||
*.png
|
||||
*.svg
|
||||
*.prisma
|
||||
*.sqlite
|
||||
yarn.lock
|
||||
.husky/pre-commit
|
||||
.prettierignore
|
||||
|
|
|
@ -41,7 +41,8 @@ model User {
|
|||
role String @default("user")
|
||||
status Int @default(0)
|
||||
updatedAt DateTime? @updatedAt
|
||||
username String @unique
|
||||
username String
|
||||
lusername String @unique
|
||||
|
||||
@@index([ihash])
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -32,6 +32,7 @@ console.log(' 🕺 ', Object.keys(data.people).length, 'people')
|
|||
console.log(' 👕 ', Object.keys(data.patterns).length, 'patterns')
|
||||
console.log(' 📰 ', data.subscribers.length, 'subscribers')
|
||||
console.log()
|
||||
data.lusernames = {}
|
||||
data.userhandles = {}
|
||||
await migrateUsers(data.users)
|
||||
console.log()
|
||||
|
@ -125,7 +126,9 @@ async function migrateUsers(users) {
|
|||
|
||||
async function createUser(user) {
|
||||
const ehash = hash(clean(user.email))
|
||||
const record = await prisma.user.create({
|
||||
let record
|
||||
try {
|
||||
record = await prisma.user.create({
|
||||
data: {
|
||||
consent: user.consent,
|
||||
createdAt: user.createdAt,
|
||||
|
@ -143,9 +146,15 @@ async function createUser(user) {
|
|||
role: user.role,
|
||||
status: user.status,
|
||||
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.lusernames[record.lusername] = user
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -28,6 +28,7 @@ const asAccount = (user, decrypt) => ({
|
|||
status: user.status,
|
||||
updatedAt: user.updatedAt,
|
||||
username: user.username,
|
||||
lusername: user.lusername,
|
||||
})
|
||||
|
||||
const getToken = (user, config) =>
|
||||
|
@ -72,6 +73,7 @@ UserController.prototype.signup = async (req, res, tools) => {
|
|||
// It does not. Creating user entry
|
||||
let record
|
||||
try {
|
||||
const username = clean(randomString()) // Temporary username,
|
||||
record = await prisma.user.create({
|
||||
data: {
|
||||
data: asJson({ settings: { language: req.body.language } }),
|
||||
|
@ -80,7 +82,8 @@ UserController.prototype.signup = async (req, res, tools) => {
|
|||
ihash: emailhash,
|
||||
initial: encrypt(clean(req.body.email)),
|
||||
password: asJson(hashPassword(req.body.password)),
|
||||
username: randomString(), // Temporary username,
|
||||
username,
|
||||
lusername: username.toLowerCase(),
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
|
@ -94,6 +97,7 @@ UserController.prototype.signup = async (req, res, tools) => {
|
|||
where: { id: record.id },
|
||||
data: {
|
||||
username: `user-${record.id}`,
|
||||
lusername: `user-${record.id}`,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
|
@ -229,47 +233,50 @@ UserController.prototype.confirm = async (req, res, tools) => {
|
|||
}
|
||||
|
||||
/*
|
||||
UserController.prototype.login = function (req, res, prisma, config) {
|
||||
if (!req.body || !req.body.username) return res.sendStatus(400)
|
||||
User.findOne(
|
||||
{
|
||||
$or: [
|
||||
{ username: req.body.username.toLowerCase().trim() },
|
||||
{ username: req.body.username.trim() },
|
||||
{ ehash: ehash(req.body.username) },
|
||||
* Login (with username and password)
|
||||
*
|
||||
* This is the endpoint that provides traditional username/password login
|
||||
* See: https://freesewing.dev/reference/backend/api
|
||||
*/
|
||||
UserController.prototype.login = async function (req, res, tools) {
|
||||
if (Object.keys(req.body) < 1) return res.status(400).json({ error: 'postBodyMissing', result })
|
||||
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
|
||||
UserController.prototype.confirmationLogin = function (req, res) {
|
||||
|
|
|
@ -3,20 +3,16 @@ import { UserController } from '../controllers/user.mjs'
|
|||
const User = new UserController()
|
||||
const jwt = ['jwt', { session: false }]
|
||||
|
||||
|
||||
export function userRoutes(tools) {
|
||||
const { app, passport } = tools
|
||||
// Sign up
|
||||
app.post(
|
||||
'/signup',
|
||||
(req, res) => User.signup(req, res, tools)
|
||||
)
|
||||
app.post('/signup', (req, res) => User.signup(req, res, tools))
|
||||
|
||||
// Confirm account
|
||||
app.post(
|
||||
'/confirm/signup/:id',
|
||||
(req, res) => User.confirm(req, res, tools)
|
||||
)
|
||||
app.post('/confirm/signup/:id', (req, res) => User.confirm(req, res, tools))
|
||||
|
||||
// Login
|
||||
app.post('/login', (req, res) => User.login(req, res, tools))
|
||||
|
||||
// Create account
|
||||
//app.post(
|
||||
|
@ -79,11 +75,6 @@ export function userRoutes(tools) {
|
|||
(req, res) => User.resend(req, res, params)
|
||||
)
|
||||
|
||||
// Login
|
||||
app.post(
|
||||
'/login',
|
||||
(req, res) => User.login(req, res, params)
|
||||
)
|
||||
|
||||
// Get list of patrons
|
||||
app.get(
|
||||
|
@ -105,4 +96,3 @@ export function userRoutes(tools) {
|
|||
)
|
||||
*/
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,17 @@ import { randomString } from '../src/utils/crypto.mjs'
|
|||
const config = verifyConfig()
|
||||
const expect = chai.expect
|
||||
chai.use(http)
|
||||
const user = '🧑'
|
||||
|
||||
const email = `test_${randomString()}@mailtrap.freesewing.dev`
|
||||
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', () => {
|
||||
it('Should return 400 on signup without body', (done) => {
|
||||
describe(`${user} Signup flow and authentication`, () => {
|
||||
it(`${user} Should return 400 on signup without body`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.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) => {
|
||||
it(`Should not create signup without ${key}`, (done) => {
|
||||
it(`${user} Should not allow signup without ${key}`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.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
|
||||
.request(config.api)
|
||||
.post('/signup')
|
||||
|
@ -68,13 +67,13 @@ describe('Non language-specific User controller signup routes', () => {
|
|||
expect(res.type).to.equal('application/json')
|
||||
expect(res.charset).to.equal('utf-8')
|
||||
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
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
step(`should confirm a new user (${email})`, (done) => {
|
||||
step(`${user} Should confirm new user (${data.email})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.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.account.id).to.equal(`number`)
|
||||
store.token = res.body.token
|
||||
store.username = res.body.account.username
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
step('should fail to signup an existing email address', (done) => {
|
||||
step(`${user} Should fail to signup an existing email address`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.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()
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue