diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js new file mode 100644 index 0000000..0c18c81 --- /dev/null +++ b/server/src/api/controllers/authController.js @@ -0,0 +1,51 @@ +import User from '../../models/user.js' +import { genJWT, hashPassword } from '../../utils/auth.js' + +export async function issueToken(req, res) { + const { user } = req + const token = await genJWT({ + _id: user.id, + username: user.username, + email: user.email, + }) + res.json({ token }) +} + +export async function checkUsername(req, res) { + const { username } = req.query + const exists = await User.exists({ username }) + if (exists) { + res.status(409).json({ + message: `"${username}" already exists`, + }) + } else { + res.status(200).json({ + message: `"${username}" is available`, + }) + } +} + +export async function register(req, res, next) { + const { username, password, email } = req.body + if (!(username && password && email)) { + res.status(400).json({ + message: 'username, password, and email are required', + }) + return + } + if (await User.exists({ $or: [{ username }, { email }] })) { + res.status(409).json({ + message: 'username or email already exists', + }) + return + } + const user = new User({ + username, + email, + }) + user.hashedPassword = await hashPassword(password) + await user.save() + req.user = user + res.status(201) + next() +} diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index 54bcc74..be4cb8c 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -1,24 +1,22 @@ import { Router } from 'express' import passport from 'passport' -import { genJWT } from '../../utils/auth.js' +import { + issueToken, + register, + checkUsername, +} from '../controllers/authController.js' const router = Router() -async function issueToken(req, res) { - const { user } = req - const token = await genJWT({ - _id: user.id, - username: user.username, - email: user.email, - }) - res.json({ token }) -} - router.post( - '/login/password', + '/login', passport.authenticate('local', { session: false }), issueToken ) +router.post('/register', register, issueToken) + +router.get('/check', checkUsername) + export default router diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 20db430..0c801f3 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -11,7 +11,7 @@ router.get('/', (req, res) => { res.send('Hello API!') }) -router.use(authRouter) +router.use('/auth', authRouter) router.use('/quests', quests) router.use( '/sessions', diff --git a/server/src/index.js b/server/src/index.js index 8a898ea..20e8af4 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -27,6 +27,7 @@ async function main() { const file = await fs.readFile('./swagger.yaml', 'utf8') const swaggerDocument = YAML.parse(file) + app.use(errorHandler) app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument)) app.use(express.json()) @@ -36,8 +37,6 @@ async function main() { app.use('/api/v1', apiRouter) - app.use(errorHandler) - server.listen(config.port, config.host, () => { console.log(`Server listening at http://${config.host}:${config.port}`) }) diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index cc8c97d..3e29a82 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -3,6 +3,10 @@ import bcrypt from 'bcrypt' import config from '../config.js' import User from '../models/user.js' +export function hashPassword(password) { + return bcrypt.hash(password, 10) +} + export async function createTestUser() { let user = await User.findOne({ username: 'testUser' }) if (!user) { @@ -10,7 +14,7 @@ export async function createTestUser() { username: 'testUser', email: 'test@example.com', }) - user.hashedPassword = await bcrypt.hash('testPassword', 10) + user.hashedPassword = await hashPassword('testPassword') await user.save() } } diff --git a/server/swagger.yaml b/server/swagger.yaml index 9f3dfe6..564ab6d 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -150,7 +150,7 @@ paths: type: string example: 'Unauthorized.' - /register: + /auth/register: post: summary: Register a new user requestBody: @@ -162,10 +162,16 @@ paths: properties: username: type: string + format: username + example: alex email: type: string + format: email + example: alex@example.com password: type: string + format: password + example: 123456 responses: '201': description: User created @@ -174,9 +180,10 @@ paths: schema: type: object properties: - message: + token: type: string - example: User created successfully + description: Access token + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad Request content: @@ -198,7 +205,7 @@ paths: type: string example: User already exists - /login/password: + /auth/login: post: summary: Log in a user requestBody: @@ -210,8 +217,12 @@ paths: properties: username: type: string + format: username + example: alex password: type: string + format: password + example: 123456 responses: '200': description: User logged in @@ -220,12 +231,10 @@ paths: schema: type: object properties: - message: - type: string - example: Login successful token: type: string description: Access token + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad Request content: @@ -247,6 +256,40 @@ paths: type: string example: Incorrect username or password + /auth/check: + get: + summary: Check if the username is already taken + parameters: + - in: query + name: username + description: username to check + required: true + schema: + type: string + format: username + example: alex + responses: + '200': + description: Username is available + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is available + '409': + description: Username is already taken + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is already taken + /profile: get: summary: Get the profile of current logged-in user