wip(backend): Account retrieval and doubles
This commit is contained in:
parent
553ab91d26
commit
dc82faee73
6 changed files with 124 additions and 102 deletions
51
sites/backend/scripts/find-duplicate-usernames.mjs
Normal file
51
sites/backend/scripts/find-duplicate-usernames.mjs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: This is not intended to work for you
|
||||||
|
*
|
||||||
|
* This script imports a raw database dump of the current (v2)
|
||||||
|
* FreeSewing backend and checks for duplicate usernames now that
|
||||||
|
* we treat them as case-insensitive.
|
||||||
|
*
|
||||||
|
* This is not the kind of thing you should try to run yourself
|
||||||
|
* because for one thing you do not have a raw database dump
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Dumped data folder
|
||||||
|
const dir = '/home/joost/'
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
// Load filtered data for migration
|
||||||
|
const file = 'freesewing-filtered.json'
|
||||||
|
const data = JSON.parse(fs.readFileSync(path.resolve(dir, file), { encoding: 'utf-8' }))
|
||||||
|
console.log()
|
||||||
|
console.log('Checking:')
|
||||||
|
console.log(' 🧑 ', Object.keys(data.users).length, 'users')
|
||||||
|
console.log()
|
||||||
|
data.lusernames = {}
|
||||||
|
await checkUsers(data.users)
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
async function checkUsers(users) {
|
||||||
|
const total = Object.keys(users).length
|
||||||
|
let i = 0
|
||||||
|
for (const user of Object.values(users)) {
|
||||||
|
i++
|
||||||
|
await checkUser(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkUser(user) {
|
||||||
|
const lusername = user.username.toLowerCase()
|
||||||
|
if (typeof data.lusernames[lusername] === 'undefined') {
|
||||||
|
data.lusernames[lusername] = user
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
const first = data.lusernames[lusername]
|
||||||
|
console.log(chalk.yellow(`${i}: ${lusername}`))
|
||||||
|
console.log(` - First by: ${chalk.green(first.handle)} / ${chalk.green(first.email)}`)
|
||||||
|
console.log(` - Later by: ${chalk.cyan(user.handle)} / ${chalk.cyan(user.email)}`)
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,6 @@ 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()
|
||||||
|
@ -127,34 +126,39 @@ async function migrateUsers(users) {
|
||||||
async function createUser(user) {
|
async function createUser(user) {
|
||||||
const ehash = hash(clean(user.email))
|
const ehash = hash(clean(user.email))
|
||||||
let record
|
let record
|
||||||
|
const _data = {
|
||||||
|
consent: user.consent,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
data: JSON.stringify(user.data),
|
||||||
|
ehash,
|
||||||
|
email: encrypt(clean(user.email)),
|
||||||
|
ihash: ehash,
|
||||||
|
initial: encrypt(clean(user.email)),
|
||||||
|
newsletter: user.newsletter,
|
||||||
|
password: JSON.stringify({
|
||||||
|
type: 'v2',
|
||||||
|
data: user.password,
|
||||||
|
}),
|
||||||
|
patron: user.patron,
|
||||||
|
role: user.role,
|
||||||
|
status: user.status,
|
||||||
|
username: user.username,
|
||||||
|
lusername: user.username.toLowerCase(),
|
||||||
|
lastLogin: new Date(user.lastLogin),
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
record = await prisma.user.create({
|
record = await prisma.user.create({ data: _data })
|
||||||
data: {
|
|
||||||
consent: user.consent,
|
|
||||||
createdAt: user.createdAt,
|
|
||||||
data: JSON.stringify(user.data),
|
|
||||||
ehash,
|
|
||||||
email: encrypt(clean(user.email)),
|
|
||||||
ihash: ehash,
|
|
||||||
initial: encrypt(clean(user.email)),
|
|
||||||
newsletter: user.newsletter,
|
|
||||||
password: JSON.stringify({
|
|
||||||
type: 'v2',
|
|
||||||
data: user.password,
|
|
||||||
}),
|
|
||||||
patron: user.patron,
|
|
||||||
role: user.role,
|
|
||||||
status: user.status,
|
|
||||||
username: user.username,
|
|
||||||
lusername: user.username.toLowerCase(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(user, err, data.lusernames[user.username.toLowerCase()])
|
_data.username += ' 2'
|
||||||
process.exit()
|
_data.lusername += ' 2'
|
||||||
|
try {
|
||||||
|
record = await prisma.user.create({ data: _data })
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data.userhandles[user.handle] = record.id
|
data.userhandles[user.handle] = record.id
|
||||||
data.lusernames[record.lusername] = user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -116,6 +116,7 @@ function migrateUser(entry) {
|
||||||
initial: entry.initial,
|
initial: entry.initial,
|
||||||
newsletter: entry.newsletter,
|
newsletter: entry.newsletter,
|
||||||
img: entry.img,
|
img: entry.img,
|
||||||
|
lastLogin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,14 +209,14 @@ UserController.prototype.confirm = async (req, res, tools) => {
|
||||||
// Update user consent and status
|
// Update user consent and status
|
||||||
let updateUser
|
let updateUser
|
||||||
try {
|
try {
|
||||||
updateUser = prisma.user.update({
|
updateUser = await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: data.id,
|
id: account.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
status: 1,
|
status: 1,
|
||||||
consent: req.body.consent,
|
consent: req.body.consent,
|
||||||
lastLogin: 'CURRENT_TIMESTAMP',
|
lastLogin: new Date(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -302,6 +302,37 @@ UserController.prototype.login = async function (req, res, tools) {
|
||||||
account: asAccount({ ...account }, decrypt),
|
account: asAccount({ ...account }, decrypt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserController.prototype.readAccount = async (req, res, tools) => {
|
||||||
|
if (!req.user?._id) return res.status(400).send({ error: 'bearerMissing', result })
|
||||||
|
|
||||||
|
// Destructure what we need from tools
|
||||||
|
const { prisma, decrypt } = tools
|
||||||
|
|
||||||
|
// Retrieve user account
|
||||||
|
let account
|
||||||
|
try {
|
||||||
|
account = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: req.user._id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
log.warn(err, `Could not lookup user id ${req.user._id} from token data`)
|
||||||
|
return res.status(404).send({ error: 'failedToRetrieveUserIdFromTokenData', result })
|
||||||
|
}
|
||||||
|
if (!account) {
|
||||||
|
log.warn(err, `Could not find user id ${req.user._id} from token data`)
|
||||||
|
return res.status(404).send({ error: 'failedToLoadUserFromTokenData', result })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return account data
|
||||||
|
return res.status(200).send({
|
||||||
|
result: 'success',
|
||||||
|
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
|
||||||
|
@ -365,28 +396,6 @@ UserController.prototype.create = (req, res) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
UserController.prototype.readAccount = (req, res) => {
|
|
||||||
if (!req.user._id) return res.sendStatus(400)
|
|
||||||
User.findById(req.user._id, (err, user) => {
|
|
||||||
if (user !== null) {
|
|
||||||
log.info('ping', { user, req })
|
|
||||||
const people = {}
|
|
||||||
Person.find({ user: user.handle }, (err, personList) => {
|
|
||||||
if (err) return res.sendStatus(400)
|
|
||||||
for (let person of personList) people[person.handle] = person.info()
|
|
||||||
const patterns = {}
|
|
||||||
Pattern.find({ user: user.handle }, (err, patternList) => {
|
|
||||||
if (err) return res.sendStatus(400)
|
|
||||||
for (let pattern of patternList) patterns[pattern.handle] = pattern.export()
|
|
||||||
return res.send({ account: user.account(), people, patterns })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return res.sendStatus(400)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserController.prototype.readProfile = (req, res) => {
|
UserController.prototype.readProfile = (req, res) => {
|
||||||
User.findOne({ username: req.params.username }, (err, user) => {
|
User.findOne({ username: req.params.username }, (err, user) => {
|
||||||
if (err) return res.sendStatus(404)
|
if (err) return res.sendStatus(404)
|
||||||
|
|
|
@ -14,6 +14,11 @@ export function userRoutes(tools) {
|
||||||
// Login
|
// Login
|
||||||
app.post('/login', (req, res) => User.login(req, res, tools))
|
app.post('/login', (req, res) => User.login(req, res, tools))
|
||||||
|
|
||||||
|
// Read account (own data)
|
||||||
|
app.get('/account', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
User.readAccount(req, res, tools)
|
||||||
|
)
|
||||||
|
|
||||||
// Create account
|
// Create account
|
||||||
//app.post(
|
//app.post(
|
||||||
// '/account',
|
// '/account',
|
||||||
|
@ -21,12 +26,6 @@ export function userRoutes(tools) {
|
||||||
//)
|
//)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Read account (own data)
|
|
||||||
app.get(
|
|
||||||
'/account',
|
|
||||||
passport.authenticate(...jwt),
|
|
||||||
(req, res) => User.readAccount(req, res, params)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update account
|
// Update account
|
||||||
app.put(
|
app.put(
|
||||||
|
|
|
@ -240,15 +240,11 @@ describe(`${user} Signup flow and authentication`, () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
step(`${user} Should load account with JWT`, (done) => {
|
||||||
step(`${user} Should login with email address and password`, (done) => {
|
|
||||||
chai
|
chai
|
||||||
.request(config.api)
|
.request(config.api)
|
||||||
.post('/login')
|
.get('/account')
|
||||||
.send({
|
.set('Authorization', 'Bearer ' + store.token)
|
||||||
username: store.username,
|
|
||||||
password: data.email,
|
|
||||||
})
|
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
expect(res.status).to.equal(200)
|
expect(res.status).to.equal(200)
|
||||||
expect(res.type).to.equal('application/json')
|
expect(res.type).to.equal('application/json')
|
||||||
|
@ -257,52 +253,14 @@ describe(`${user} Signup flow and authentication`, () => {
|
||||||
expect(res.body.account.email).to.equal(data.email)
|
expect(res.body.account.email).to.equal(data.email)
|
||||||
expect(res.body.account.username).to.equal(store.username)
|
expect(res.body.account.username).to.equal(store.username)
|
||||||
expect(res.body.account.lusername).to.equal(store.username.toLowerCase())
|
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`)
|
expect(typeof res.body.account.id).to.equal(`number`)
|
||||||
store.token = res.body.token
|
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
describe('Login/Logout and session handling', () => {
|
|
||||||
|
|
||||||
it('should login with the email address', (done) => {
|
|
||||||
chai
|
|
||||||
.request(config.backend)
|
|
||||||
.post('/login')
|
|
||||||
.send({
|
|
||||||
username: config.user.email,
|
|
||||||
password: config.user.password,
|
|
||||||
})
|
|
||||||
.end((err, res) => {
|
|
||||||
res.should.have.status(200)
|
|
||||||
let data = JSON.parse(res.text)
|
|
||||||
data.account.username.should.equal(config.user.username)
|
|
||||||
data.token.should.be.a('string')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should load account with JSON Web Token', (done) => {
|
|
||||||
chai
|
|
||||||
.request(config.backend)
|
|
||||||
.get('/account')
|
|
||||||
.set('Authorization', 'Bearer ' + config.user.token)
|
|
||||||
.end((err, res) => {
|
|
||||||
if (err) console.log(err)
|
|
||||||
let data = JSON.parse(res.text)
|
|
||||||
res.should.have.status(200)
|
|
||||||
data.account.username.should.equal(config.user.username)
|
|
||||||
// Enable this once cleanup is implemented
|
|
||||||
//Object.keys(data.recipes).length.should.equal(0)
|
|
||||||
//Object.keys(data.people).length.should.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Account management', () => {
|
describe('Account management', () => {
|
||||||
it('should update the account avatar', (done) => {
|
it('should update the account avatar', (done) => {
|
||||||
chai
|
chai
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue