Skip to content

Commit

Permalink
Enforce single user per email, usernames case-insensitive (bluesky-so…
Browse files Browse the repository at this point in the history
…cial#217)

* Schemas and scaffolding for reset password methods

* Initial handler for todo.adx.requestAccountPasswordReset

* Initial handler for todo.adx.resetAccountPassword

* Implement server mailer

* Configure server for mailer and testing w/ mailer

* Test happy path of pass reset, fix reset bug

* Update lex to fix types bug for requestAccountPasswordReset

* Fix handlebars reference to config getters

* Test some negative password reset flows

* Minor cleanup to pass reset

* Tidy handlebars file with prettier, supporting double-quotes for html

* Fix esbuild of server for mailer templates, fix test issue

* Misc tidying for password reset

* Misc tidying for password reset

* Enforce single user per email, test unique email and username

* Remove resolved TODO re: duplicate emails
  • Loading branch information
devinivy authored Oct 6, 2022
1 parent 59f18ac commit e30c45d
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 9 deletions.
16 changes: 13 additions & 3 deletions packages/server/src/api/todo/adx/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,22 @@ export default function (server: Server) {
isTestUser = true
}

// verify username is available
const found = await db.getUser(username)
if (found !== null) {
// verify username and email are available.

// @TODO consider pushing this to the db, and checking for a
// uniqueness error during registerUser(). Main blocker to doing
// that now is that we need to create a did prior to registration.

const foundUsername = await db.getUser(username)
if (foundUsername !== null) {
throw new InvalidRequestError(`Username already taken: ${username}`)
}

const foundEmail = await db.getUserByEmail(email)
if (foundEmail !== null) {
throw new InvalidRequestError(`Email already taken: ${email}`)
}

const plcClient = new PlcClient(config.didPlcUrl)
let did: string
try {
Expand Down
5 changes: 2 additions & 3 deletions packages/server/src/api/todo/adx/password-reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ export default function (server: Server) {
const { db, mailer, config } = locals.get(res)
const { email } = input.body

// @TODO should multiple users be able to have the same email?
const table = db.db.getRepository(User)
const users = await table.findBy({ email })
const user = await table.findOneBy({ email })

for (const user of users) {
if (user) {
// By signing with the password hash, this jwt becomes invalid once the user changes their password.
// This allows us to ensure it's essentially single-use (combined with the jwt's short-livedness).
const token = jwt.sign(
Expand Down
8 changes: 8 additions & 0 deletions packages/server/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ export class Database {
return found ? { username: found.username, did: found.did } : null
}

async getUserByEmail(
email: string,
): Promise<{ username: string; did: string } | null> {
const table = this.db.getRepository(User)
const found = await table.findOneBy({ email })
return found ? { username: found.username, did: found.did } : null
}

async getUserDid(usernameOrDid: string): Promise<string | null> {
if (usernameOrDid.startsWith('did:')) return usernameOrDid
const found = await this.getUser(usernameOrDid)
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/db/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export class User {
@PrimaryColumn('varchar')
did: string

@Column({ type: 'varchar', unique: true })
@Column({ type: 'varchar', unique: true, collation: 'nocase' })
@Index()
username: string

@Column({ type: 'varchar', collation: 'nocase' })
@Column({ type: 'varchar', unique: true, collation: 'nocase' })
email: string

@Column('varchar')
Expand Down
43 changes: 43 additions & 0 deletions packages/server/tests/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,49 @@ describe('account', () => {
expect(res.data.username).toEqual(username)
})

it('disallows duplicate email addresses and usernames', async () => {
const res = await client.todo.adx.createInviteCode(
{},
{ useCount: 2 },
{
headers: { authorization: util.adminAuth() },
encoding: 'application/json',
},
)
const inviteCode = res.data.code
const email = '[email protected]'
const username = 'bob.test'
const password = 'test123'
await client.todo.adx.createAccount(
{},
{ email, username, password, inviteCode },
)

await expect(
client.todo.adx.createAccount(
{},
{
email: email.toUpperCase(),
username: 'carol.test',
password,
inviteCode,
},
),
).rejects.toThrow('Email already taken: [email protected]')

await expect(
client.todo.adx.createAccount(
{},
{
email: '[email protected]',
username: username.toUpperCase(),
password,
inviteCode,
},
),
).rejects.toThrow('Username already taken: BOB.TEST')
})

it('fails on used up invite code', async () => {
const promise = client.todo.adx.createAccount(
{},
Expand Down
2 changes: 1 addition & 1 deletion packages/server/tests/crud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const alice = {
password: 'alice-pass',
}
const bob = {
email: 'alice@test.com',
email: 'bob@test.com',
username: 'bob.test',
did: '',
password: 'bob-pass',
Expand Down

0 comments on commit e30c45d

Please sign in to comment.