feat: pick username

* put new registered social login user to session data
* ask to choose username
* simplify checkUsername functions
This commit is contained in:
wancat committed 2023-10-13 00:05:59 +08:00
1 parent 5591689816
commit 182fcf643f
12 files changed
+244 -69

No files matched your search

+66 -23
View File
@@ -16,6 +16,14 @@ export async function issueToken(req, res) {
res.json({ token })
}
async function invalidUsername(username) {
return !isValidUsername(username) || User.exists({ username })
}
async function invalidEmail(email) {
return !isValidEmail(email) || User.exists({ email })
}
export async function checkUsername(req, res) {
const { username, email } = req.query
const cred = username || email
@@ -27,30 +35,16 @@ export async function checkUsername(req, res) {
}
if (isValidEmail(cred)) {
if (await User.exists({ email: cred })) {
res.status(409).json({
type: 'email',
message: `"${cred}" already exists`,
})
return
}
res.status(200).json({
res.json({
type: 'email',
message: `"${cred}" is available`,
available: !(await User.exists({ email: cred })),
})
return
}
if (isValidUsername(cred)) {
if (await User.exists({ username: cred })) {
res.status(409).json({
type: 'username',
message: `"${cred}" already exists`,
})
return
}
res.status(200).json({
res.json({
type: 'username',
message: `"${cred}" is available`,
available: !(await User.exists({ username: cred })),
})
return
}
@@ -68,18 +62,18 @@ export async function register(req, res, next) {
return
}
if (await User.exists({ username })) {
if (await invalidUsername(username)) {
res.status(409).json({
type: 'username',
message: 'username already exists',
message: 'Invalid username',
})
return
}
if (await User.exists({ email })) {
if (await invalidEmail(email)) {
res.status(409).json({
type: 'email',
message: 'email already exists',
message: 'Invalid email',
})
return
}
@@ -126,6 +120,55 @@ export function logout(req, res) {
export function checkSession(req, res) {
res.json({
loggedIn: req.isAuthenticated(),
loggedIn: req.isAuthenticated() && !req.session.newUser,
})
}
export async function socialLogin(req, res) {
const { newUser } = req.user
if (newUser) {
req.session.newUser = newUser
res.redirect('/choose-username')
return
}
res.redirect('/')
}
export async function registerFromSession(req, res) {
const { username } = req.body
const { newUser } = req.session
if (!newUser) {
res.status(400).json({
message: 'no new user found',
})
return
}
if (await invalidUsername(username)) {
res.status(409).json({
type: 'username',
message: 'Invalid username',
})
return
}
newUser.username = username
const user = new User(newUser)
await user.save()
req.login(user, (err) => {
if (err) {
console.error(err)
res.status(500).json({
message: 'error logging in',
})
return
}
delete req.session.newUser
res.status(201).json({
message: 'user created',
})
})
}
+6 -14
View File
@@ -8,6 +8,8 @@ import {
checkUsername,
checkSession,
logout,
socialLogin,
registerFromSession,
} from '../controllers/authController.js'
const router = Router()
@@ -34,24 +36,14 @@ router.get('/available-methods', (req, res) => {
if (enabledMethods.google) {
router.get('/google', passport.authenticate('google'))
router.get(
'/google/callback',
passport.authenticate('google', {
successRedirect: '/',
failureRedirect: '/login',
})
)
router.get('/google/callback', passport.authenticate('google'), socialLogin)
}
if (enabledMethods.github) {
router.get('/github', passport.authenticate('github'))
router.get(
'/github/callback',
passport.authenticate('github', {
successRedirect: '/',
failureRedirect: '/login',
})
)
router.get('/github/callback', passport.authenticate('github'), socialLogin)
}
router.post('/register-from-session', registerFromSession)
export default router
+7 -7
View File
@@ -5,8 +5,6 @@ import User from '../models/user.js'
function verify(accessToken, refreshToken, profile, cb) {
process.nextTick(async () => {
console.log(accessToken, refreshToken, profile)
// cb('todo')
const email = profile.emails[0]?.value
if (!email) {
cb(new Error('No email found'))
@@ -21,16 +19,18 @@ function verify(accessToken, refreshToken, profile, cb) {
displayName: profile.displayName,
}
if (!user) {
user = new User({
const newUser = {
email,
github,
})
} else if (user.github?.id) {
}
cb(null, { newUser })
return
}
if (user.github?.id) {
cb(new Error('Email already used and binded to another account'))
return
} else {
user.github = github
}
user.github = github
await user.save()
}
+6 -4
View File
@@ -19,16 +19,18 @@ function verify(issuer, profile, cb) {
displayName: profile.displayName,
}
if (!user) {
user = new User({
const newUser = new User({
email,
google,
})
} else if (user.google?.id) {
cb(null, { newUser })
return
}
if (user.google?.id) {
cb(new Error('Email already used and binded to another account'))
return
} else {
user.google = google
}
user.google = google
await user.save()
}
+1 -1
View File
@@ -1,5 +1,5 @@
export default function authRequired(req, res, next) {
if (req.isAuthenticated()) {
if (req.isAuthenticated() && !req.session.newUser) {
next()
return
}
+1 -2
View File
@@ -4,8 +4,7 @@ const User = model('User', {
username: {
type: String,
unique: true,
required: false, // should be required,
// but need to let social login users to pick one after login
required: true,
},
email: {
type: String,
+1 -1
View File
@@ -56,7 +56,7 @@ export function isValidUsername(username) {
if (!usernamePattern.test(username)) return false
// check if the username is between 1 and 32 characters
if (username.length < 1 || username.length > 32) {
if (username.length < 4 || username.length > 32) {
return false
}
+40 -4
View File
@@ -211,6 +211,31 @@ paths:
type: string
example: User already exists
/auth/register-from-session:
post:
summary: Register a new user from a session, for social login to pick a username
security:
- cookieAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
format: username
example: alex
responses:
'201':
description: User created
headers:
Set-Cookie:
schema:
type: string
example: connect.sid=s%3Aq1Z; Path=/; HttpOnly; SameSite=Strict; Secure
/auth/login:
post:
summary: Log in a user
@@ -297,19 +322,30 @@ paths:
type: string
format: username
example: alex
- in: query
name: email
description: email to check
required: true
schema:
type: string
format: email
example: alex@example.com
responses:
'200':
description: Username is available
description: Username / Email checking result
content:
application/json:
schema:
type: object
properties:
message:
type:
type: string
example: Username is available
'409':
description: Username is already taken
available:
type: boolean
example: true
'400':
description: Bad Request
content:
application/json:
schema: