|
@@ -1,14 +1,15 @@
|
|
|
import * as v from '../validate'
|
|
|
-import type { Account } from './types'
|
|
|
+import type { AuthRequestHandler } from '../auth'
|
|
|
import type { Context } from '../types'
|
|
|
import { ObjectId } from 'mongodb'
|
|
|
import type { RequestHandler } from 'express'
|
|
|
import type { WithId } from 'mongodb'
|
|
|
-import { sendBadRequest, sendNotFound } from '../http'
|
|
|
+import type { Account, AccountCreate, AccountUpdate } from './types'
|
|
|
+import { sendBadRequest, sendForbidden, sendNotFound, sendUnauthorized } from '../http'
|
|
|
|
|
|
export function createAccount({ model }: Context): RequestHandler {
|
|
|
interface RequestData {
|
|
|
- account: Account
|
|
|
+ account: AccountCreate
|
|
|
}
|
|
|
|
|
|
interface ResponseData {
|
|
@@ -18,6 +19,7 @@ export function createAccount({ model }: Context): RequestHandler {
|
|
|
const readRequestData = v.validate<RequestData>({
|
|
|
account: {
|
|
|
email: v.email,
|
|
|
+ password: v.seq(v.minLength(8)),
|
|
|
},
|
|
|
})
|
|
|
|
|
@@ -26,6 +28,7 @@ export function createAccount({ model }: Context): RequestHandler {
|
|
|
const input = readRequestData(req.body)
|
|
|
|
|
|
const account = await model.account.create(input.account)
|
|
|
+ if (!account) return sendNotFound(res, next, { reason: 'unexpectedly failed to get new account' })
|
|
|
|
|
|
const output: ResponseData = { account }
|
|
|
res.send(output)
|
|
@@ -41,15 +44,17 @@ export function createAccount({ model }: Context): RequestHandler {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function deleteAccount({ model }: Context): RequestHandler {
|
|
|
+export function deleteAccount({ model }: Context): AuthRequestHandler {
|
|
|
interface ResponseData {
|
|
|
account: WithId<Account>
|
|
|
}
|
|
|
|
|
|
return async function (req, res, next) {
|
|
|
- /** @todo get ID from authentication */
|
|
|
- const id = req.params.id
|
|
|
+ if (!req.verified) return sendUnauthorized(res, next)
|
|
|
+
|
|
|
+ const id = req.params.id || req.accountId
|
|
|
if (!id) return sendBadRequest(res, next, { reason: 'no ID' })
|
|
|
+ if (!req.accountId?.equals(id)) return sendForbidden(res, next)
|
|
|
|
|
|
try {
|
|
|
/** @todo delete related data */
|
|
@@ -65,15 +70,17 @@ export function deleteAccount({ model }: Context): RequestHandler {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function getAccount({ model }: Context): RequestHandler {
|
|
|
+export function getAccount({ model }: Context): AuthRequestHandler {
|
|
|
interface ResponseData {
|
|
|
account: WithId<Account>
|
|
|
}
|
|
|
|
|
|
return async function (req, res, next) {
|
|
|
- /** @todo get ID from authentication */
|
|
|
- const id = req.params.id
|
|
|
+ if (!req.verified) return sendUnauthorized(res, next)
|
|
|
+
|
|
|
+ const id = req.params.id || req.accountId
|
|
|
if (!id) return sendBadRequest(res, next, { reason: 'no ID' })
|
|
|
+ if (!req.accountId?.equals(id)) return sendForbidden(res, next)
|
|
|
|
|
|
try {
|
|
|
const account = await model.account.collection.findOne({ _id: new ObjectId(id) })
|
|
@@ -88,9 +95,52 @@ export function getAccount({ model }: Context): RequestHandler {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function updateAccount({ model }: Context): RequestHandler {
|
|
|
+export function loginAccount({ auth, model }: Context): RequestHandler {
|
|
|
interface RequestData {
|
|
|
- account: Partial<Account>
|
|
|
+ account: Pick<Account, 'email' | 'password'>
|
|
|
+ }
|
|
|
+
|
|
|
+ interface ResponseData {
|
|
|
+ token: string
|
|
|
+ account: WithId<Account>
|
|
|
+ }
|
|
|
+
|
|
|
+ const readRequestData = v.validate<RequestData>({
|
|
|
+ account: {
|
|
|
+ email: v.email,
|
|
|
+ password: v.seq(v.minLength(8)),
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ return async function (req, res, next) {
|
|
|
+ try {
|
|
|
+ const input = readRequestData(req.body)
|
|
|
+
|
|
|
+ const account = await model.account.collection.findOne({ email: input.account.email })
|
|
|
+ if (!account) return sendNotFound(res, next)
|
|
|
+
|
|
|
+ const password = model.account.hashPassword(input.account.password, account.passwordSalt)
|
|
|
+ if (password !== account.password) return sendBadRequest(res, next, { reason: 'invalid password' })
|
|
|
+
|
|
|
+ const token = await auth.sign(account._id)
|
|
|
+
|
|
|
+ const output: ResponseData = { token, account }
|
|
|
+ res.send(output)
|
|
|
+ next()
|
|
|
+ } catch (err) {
|
|
|
+ const name = (err as Error).name
|
|
|
+ if (name === 'ValidateError') {
|
|
|
+ const ve = err as v.ValidateError
|
|
|
+ return sendBadRequest(res, next, { param: ve.param, reason: ve.message })
|
|
|
+ }
|
|
|
+ return next(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function updateAccount({ model }: Context): AuthRequestHandler {
|
|
|
+ interface RequestData {
|
|
|
+ account: AccountUpdate
|
|
|
}
|
|
|
|
|
|
interface ResponseData {
|
|
@@ -100,25 +150,24 @@ export function updateAccount({ model }: Context): RequestHandler {
|
|
|
const readRequestData = v.validate<RequestData>({
|
|
|
account: {
|
|
|
email: v.seq(v.optional, v.email),
|
|
|
+ password: v.seq(v.optional, v.minLength(8)),
|
|
|
},
|
|
|
})
|
|
|
|
|
|
return async function (req, res, next) {
|
|
|
- /** @todo get ID from authentication */
|
|
|
- const id = req.params.id
|
|
|
+ if (!req.verified) return sendUnauthorized(res, next)
|
|
|
+
|
|
|
+ const id = req.params.id || req.accountId
|
|
|
if (!id) return sendBadRequest(res, next, { reason: 'no ID' })
|
|
|
+ if (!req.accountId?.equals(id)) return sendForbidden(res, next)
|
|
|
|
|
|
try {
|
|
|
const input = readRequestData(req.body)
|
|
|
- if (!input.account.email) {
|
|
|
- return sendBadRequest(res, next, { reason: 'no data' })
|
|
|
+ if (!input.account.email && !input.account.password) {
|
|
|
+ return sendBadRequest(res, next, { reason: 'no changes' })
|
|
|
}
|
|
|
|
|
|
- const account = await model.account.collection.findOneAndUpdate(
|
|
|
- { _id: new ObjectId(id) },
|
|
|
- { $set: input.account },
|
|
|
- { returnDocument: 'after' },
|
|
|
- )
|
|
|
+ const account = await model.account.update(id, input.account)
|
|
|
if (!account) return sendNotFound(res, next)
|
|
|
|
|
|
const output: ResponseData = { account }
|