Skip to content

Commit

Permalink
implement linkedin login
Browse files Browse the repository at this point in the history
  • Loading branch information
dileepainivossl committed Jul 11, 2024
1 parent 0df9fb3 commit 8361dd6
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ CLIENT_URL=http://localhost:5173
IMG_HOST=http://localhost:${SERVER_PORT}
SMTP_MAIL=your_smtp_mail
SMTP_PASSWORD=your_smtp_password
LINKEDIN_CLIENT_ID=your_linkedin_client_id
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret
LINKEDIN_REDIRECT_URL=http://localhost:${SERVER_PORT}/api/auth/linkedin/callback
3 changes: 2 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import adminRouter from './routes/admin/admin.route'
import mentorRouter from './routes/mentor/mentor.route'
import categoryRouter from './routes/category/category.route'
import passport from 'passport'
import './configs/passport'
import './configs/google-passport'
import './configs/linkedin-passport'
import { CLIENT_URL } from './configs/envConfig'
import cookieParser from 'cookie-parser'
import menteeRouter from './routes/mentee/mentee.route'
Expand Down
3 changes: 3 additions & 0 deletions src/configs/envConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export const CLIENT_URL = process.env.CLIENT_URL ?? ''
export const IMG_HOST = process.env.IMG_HOST ?? ''
export const SMTP_MAIL = process.env.SMTP_MAIL ?? ''
export const SMTP_PASS = process.env.SMTP_PASS ?? ''
export const LINKEDIN_CLIENT_ID = process.env.LINKEDIN_CLIENT_ID ?? ''
export const LINKEDIN_CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET ?? ''
export const LINKEDIN_REDIRECT_URL = process.env.LINKEDIN_REDIRECT_URL ?? ''
19 changes: 13 additions & 6 deletions src/configs/passport.ts → src/configs/google-passport.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { Request } from 'express'
import passport from 'passport'
import { Strategy as JwtStrategy } from 'passport-jwt'
import { dataSource } from './dbConfig'
import Profile from '../entities/profile.entity'
import { dataSource } from './dbConfig'
import {
JWT_SECRET,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GOOGLE_REDIRECT_URL,
GOOGLE_CLIENT_SECRET
JWT_SECRET
} from './envConfig'
import type { Request } from 'express'

import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
import { findOrCreateUser } from '../services/auth.service'
import { type User } from '../types'
import { CreateProfile, type User } from '../types'

Check failure on line 15 in src/configs/google-passport.ts

View workflow job for this annotation

GitHub Actions / build

All imports in the declaration are only used as types. Use `import type`

passport.use(
new GoogleStrategy(
Expand All @@ -31,7 +31,14 @@ passport.use(
done: (err: Error | null, user?: Profile) => void
) {
try {
const user = await findOrCreateUser(profile)
const createProfile: CreateProfile = {
id: profile.id,
primary_email: profile.emails?.[0]?.value ?? '',
first_name: profile.name?.givenName ?? '',
last_name: profile.name?.familyName ?? '',
image_url: profile.photos?.[0]?.value ?? ''
}
const user = await findOrCreateUser(createProfile)
done(null, user)
} catch (err) {
done(err as Error)
Expand Down
100 changes: 100 additions & 0 deletions src/configs/linkedin-passport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { Request } from 'express'
import passport from 'passport'
import { Strategy as JwtStrategy } from 'passport-jwt'
import Profile from '../entities/profile.entity'
import { dataSource } from './dbConfig'
import {
JWT_SECRET,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
LINKEDIN_REDIRECT_URL
} from './envConfig'

import { Strategy as LinkedInStrategy } from 'passport-linkedin-oauth2'
import { findOrCreateUser } from '../services/auth.service'
import { CreateProfile, type User } from '../types'

Check failure on line 15 in src/configs/linkedin-passport.ts

View workflow job for this annotation

GitHub Actions / build

All imports in the declaration are only used as types. Use `import type`

passport.use(
new LinkedInStrategy(
{
clientID: LINKEDIN_CLIENT_ID,
clientSecret: LINKEDIN_CLIENT_SECRET,
callbackURL: LINKEDIN_REDIRECT_URL,
scope: ['openid', 'email', 'profile'],
passReqToCallback: true
},
async function (
req: Request,
accessToken: string,
refreshToken: string,
profile: any,

Check warning on line 30 in src/configs/linkedin-passport.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
done: (err: Error | null, user?: Profile) => void
) {
try {
const createProfile: CreateProfile = {
id: profile.id,
primary_email: profile?.email ?? '',
first_name: profile?.givenName ?? '',
last_name: profile?.familyName ?? '',
image_url: profile?.picture ?? ''
}
const user = await findOrCreateUser(createProfile)
done(null, user)
} catch (err) {
done(err as Error)
}
}
)
)

passport.serializeUser((user: Express.User, done) => {
done(null, (user as User).primary_email)
})

passport.deserializeUser(async (primary_email: string, done) => {
try {
const profileRepository = dataSource.getRepository(Profile)
const user = await profileRepository.findOne({
where: { primary_email },
relations: ['mentor', 'mentee']
})
done(null, user)
} catch (err) {
done(err)
}
})

const cookieExtractor = (req: Request): string => {
let token = null
if (req?.cookies) {
token = req.cookies.jwt
}
return token
}

const options = {
jwtFromRequest: cookieExtractor,
secretOrKey: JWT_SECRET
}

passport.use(
new JwtStrategy(options, async (jwtPayload, done) => {
try {
const profileRepository = dataSource.getRepository(Profile)
const profile = await profileRepository.findOne({
where: { uuid: jwtPayload.userId },
relations: ['mentor', 'mentee']
})

if (!profile) {
done(null, false)
} else {
done(null, profile)
}
} catch (error) {
done(error, false)
}
})
)

export default passport
24 changes: 24 additions & 0 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ export const googleRedirect = async (
)(req, res, next)
}

export const linkedinRedirect = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
passport.authenticate(
'linkedin',
{ failureRedirect: '/login' },
(err: Error, user: Profile) => {
if (err) {
next(err)
return
}
if (!user) {
res.redirect('/login')
return
}
signAndSetCookie(res, user.uuid)

res.redirect(process.env.CLIENT_URL ?? '/')
}
)(req, res, next)
}

export const register = async (
req: Request,
res: Response
Expand Down
17 changes: 13 additions & 4 deletions src/routes/auth/auth.route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import express from 'express'
import passport from 'passport'
import {
register,
googleRedirect,
linkedinRedirect,
login,
logout,
googleRedirect,
passwordReset,
passwordResetRequest,
passwordReset
register
} from '../../controllers/auth.controller'
import passport from 'passport'

const authRouter = express.Router()

Expand All @@ -22,7 +23,15 @@ authRouter.get(
})
)

authRouter.get(
'/linkedin',
passport.authenticate('linkedin', {
scope: ['openid', 'email', 'profile']
})
)

authRouter.get('/google/callback', googleRedirect)
authRouter.get('/linkedin/callback', linkedinRedirect)
authRouter.post('/password-reset-request', passwordResetRequest)
authRouter.put('/passwordreset', passwordReset)
export default authRouter
28 changes: 14 additions & 14 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { dataSource } from '../configs/dbConfig'
import bcrypt from 'bcrypt'
import Profile from '../entities/profile.entity'
import type passport from 'passport'
import jwt from 'jsonwebtoken'
import { dataSource } from '../configs/dbConfig'
import { JWT_SECRET } from '../configs/envConfig'
import Profile from '../entities/profile.entity'
import { CreateProfile, type ApiResponse } from '../types'

Check failure on line 6 in src/services/auth.service.ts

View workflow job for this annotation

GitHub Actions / build

All imports in the declaration are only used as types. Use `import type`
import {
getPasswordResetEmailContent,
getPasswordChangedEmailContent
getPasswordChangedEmailContent,
getPasswordResetEmailContent
} from '../utils'
import { sendResetPasswordEmail } from './admin/email.service'
import { type ApiResponse } from '../types'

export const registerUser = async (
email: string,
Expand Down Expand Up @@ -88,21 +87,22 @@ export const loginUser = async (
}

export const findOrCreateUser = async (
profile: passport.Profile
createProfileDto: CreateProfile
): Promise<Profile> => {
const profileRepository = dataSource.getRepository(Profile)

let user = await profileRepository.findOne({
where: { primary_email: profile.emails?.[0]?.value ?? '' },
relations: ['mentor', 'mentee']
where: { primary_email: createProfileDto.primary_email }
})

if (!user) {
const hashedPassword = await bcrypt.hash(profile.id, 10) // Use Google ID as password
const hashedPassword = await bcrypt.hash(createProfileDto.id, 10)
user = profileRepository.create({
primary_email: profile.emails?.[0]?.value ?? '',
primary_email: createProfileDto.primary_email,
password: hashedPassword,
first_name: profile.name?.givenName,
last_name: profile.name?.familyName,
image_url: profile.photos?.[0]?.value ?? ''
first_name: createProfileDto.first_name,
last_name: createProfileDto.last_name,
image_url: createProfileDto.image_url
})
await profileRepository.save(user)
}
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ export interface ApiResponse<T> {
export interface User extends Express.User {
primary_email: string
}

export interface CreateProfile {
primary_email: string
id: string
first_name: string
last_name: string
image_url: string
}

0 comments on commit 8361dd6

Please sign in to comment.