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
|
*.png
|
||||||
*.svg
|
*.svg
|
||||||
*.prisma
|
*.prisma
|
||||||
|
*.sqlite
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.husky/pre-commit
|
.husky/pre-commit
|
||||||
.prettierignore
|
.prettierignore
|
||||||
|
|
|
@ -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.
|
@ -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,27 +126,35 @@ 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
|
||||||
data: {
|
try {
|
||||||
consent: user.consent,
|
record = await prisma.user.create({
|
||||||
createdAt: user.createdAt,
|
data: {
|
||||||
data: JSON.stringify(user.data),
|
consent: user.consent,
|
||||||
ehash,
|
createdAt: user.createdAt,
|
||||||
email: encrypt(clean(user.email)),
|
data: JSON.stringify(user.data),
|
||||||
ihash: ehash,
|
ehash,
|
||||||
initial: encrypt(clean(user.email)),
|
email: encrypt(clean(user.email)),
|
||||||
newsletter: user.newsletter,
|
ihash: ehash,
|
||||||
password: JSON.stringify({
|
initial: encrypt(clean(user.email)),
|
||||||
type: 'v2',
|
newsletter: user.newsletter,
|
||||||
data: user.password,
|
password: JSON.stringify({
|
||||||
}),
|
type: 'v2',
|
||||||
patron: user.patron,
|
data: user.password,
|
||||||
role: user.role,
|
}),
|
||||||
status: user.status,
|
patron: user.patron,
|
||||||
username: user.username,
|
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.userhandles[user.handle] = record.id
|
||||||
|
data.lusernames[record.lusername] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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 })
|
||||||
},
|
|
||||||
(err, user) => {
|
// Destructure what we need from tools
|
||||||
if (err) return res.sendStatus(400)
|
const { prisma, config, decrypt } = tools
|
||||||
if (user === null) return res.sendStatus(401)
|
|
||||||
user.verifyPassword(req.body.password, (err, valid) => {
|
// Retrieve user account
|
||||||
if (err) return res.sendStatus(400)
|
let account
|
||||||
else if (valid) {
|
try {
|
||||||
if (user.status !== 'active') return res.sendStatus(403)
|
account = await prisma.user.findFirst({
|
||||||
else {
|
where: {
|
||||||
log.info('login', { user, req })
|
OR: [
|
||||||
let account = user.account()
|
{ lusername: { equals: clean(req.body.username) } },
|
||||||
let token = getToken(account)
|
{ ehash: { equals: hash(clean(req.body.username)) } },
|
||||||
let people = {}
|
{ id: { equals: parseInt(req.body.username) || -1 } },
|
||||||
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 = {}
|
} catch (err) {
|
||||||
Pattern.find({ user: user.handle }, (err, patternList) => {
|
log.warn(err, `Error while trying to find username: ${req.body.username}`)
|
||||||
if (err) return res.sendStatus(400)
|
return res.status(401).send({ error: 'logingFailed', result })
|
||||||
for (let pattern of patternList) patterns[pattern.handle] = pattern
|
}
|
||||||
return user.updateLoginTime(() => res.send({ account, people, patterns, token }))
|
if (!account) {
|
||||||
})
|
log.warn(`Login attempt for non-existing user: ${req.body.username} from ${req.ip}`)
|
||||||
})
|
return res.status(401).send({ error: 'logingFailed', result })
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.warning('wrongPassword', { user, req })
|
// Login success
|
||||||
return res.sendStatus(401)
|
log.info(`Login by user ${account.id} (${account.username})`)
|
||||||
}
|
|
||||||
})
|
return res.status(200).send({
|
||||||
}
|
result: 'success',
|
||||||
)
|
token: getToken(account, config),
|
||||||
|
account: asAccount({ ...account }, decrypt),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -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) {
|
||||||
)
|
)
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue