diff --git a/.gitignore b/.gitignore index 29c0f48..7ba48c4 100644 --- a/.gitignore +++ b/.gitignore @@ -39,9 +39,6 @@ dist/ # Flycheck flycheck_*.el -# server auth directory -/server/ - # projectiles files .projectile diff --git a/docker-compose.yml b/docker-compose.yml index 1a78542..3183ab6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: image: mongo-express restart: always ports: - - 8081:8081 + - 8181:8081 environment: ME_CONFIG_MONGODB_SERVER: 'mongo' ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGODB_USER} diff --git a/server/auth/passport.ts b/server/auth/passport.ts new file mode 100644 index 0000000..82a7e7c --- /dev/null +++ b/server/auth/passport.ts @@ -0,0 +1,21 @@ +import passport from 'passport'; +import { facebookStrategy, facebookRouter } from './providers/facebook'; +import { Router } from 'express' +import UserModel, { User } from '../models/user' + +export function initPassport(): void { + passport.serializeUser((user: User, done) => { + done(null, user.id); + }); + + passport.deserializeUser((id, done) => { + UserModel.findById(id, (err, user) => { + done(err, user); + }); + }); + + passport.use('facebook', facebookStrategy()); +} + +export const authRouter = Router(); +authRouter.use(facebookRouter); diff --git a/server/auth/providers/facebook.ts b/server/auth/providers/facebook.ts new file mode 100644 index 0000000..896fae7 --- /dev/null +++ b/server/auth/providers/facebook.ts @@ -0,0 +1,27 @@ +import { Strategy, StrategyOption, AuthenticateOptions, Profile } from 'passport-facebook'; + +import { Router } from 'express' +import passport from 'passport' + +import genericStrategy from '../strategy' + +const strategyOptions: StrategyOption = { + clientID: process.env.FACEBOOK_CLIENT_ID as string, + clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, + callbackURL: '/auth/facebook/redirect', + profileFields: ['id', 'displayName', 'picture.type(large)', 'email'] +} + +export function facebookStrategy() { + return new Strategy(strategyOptions, genericStrategy('facebook')); +} + +export const facebookRouter = Router() + +const facebookAuthenticateOptions: AuthenticateOptions = { + authType: 'rerequest', scope: ['email'] +} + +facebookRouter.get('/facebook', passport.authenticate('facebook', facebookAuthenticateOptions)); +facebookRouter.get('/facebook/redirect', + passport.authenticate('facebook', { successRedirect: '/museo', failureRedirect: '/' })); diff --git a/server/auth/strategy.ts b/server/auth/strategy.ts new file mode 100644 index 0000000..c7e89a0 --- /dev/null +++ b/server/auth/strategy.ts @@ -0,0 +1,48 @@ +import { Profile } from 'passport' +import UserModel, { User } from '../models/user' +import { authProviders } from '../types' + +type doneCallback = (error: string | null, user?: Promise | User | boolean) => void + +function genericStrategy(provider: authProviders) { + return async ( + _accessToken: string, + _refreshToken: string, + profile: ProfileT, + done: doneCallback + ) => { + if (!profile.emails?.[0].value) { + console.error(`${provider} Email permission not provided`) + return done(null, false); + } + + try { + const user = await UserModel.findOne({ provider, providerId: profile.id }).exec(); + + console.info(`${provider} user found. Logging in.`); + + if (user) { + return done(null, user); + } + + console.info(`${provider} user not found. Creating.`); + + const newUser = new UserModel({ + providerId: profile.id, + name: profile.displayName, + provider, + email: profile.emails?.[0].value, + photo: profile.photos?.[0].value, + }).save((err: string) => { + if (err) throw (err); + }); + + return done(null, newUser); + + } catch (e) { + return done(e); + } + } +} + +export default genericStrategy diff --git a/server/models/user.ts b/server/models/user.ts new file mode 100644 index 0000000..16d1531 --- /dev/null +++ b/server/models/user.ts @@ -0,0 +1,36 @@ +import { model, Schema, Document } from "mongoose" +import { authProviders } from '../types' + +export interface User extends Document { + providerId: string + name: string + email: string + provider: authProviders + photo?: string +} + +const userSchema = new Schema( + { + providerId: { + type: String, + required: true + }, + provider: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + photo: String + }, + { timestamps: true } +) + +const UserModel = model("User", userSchema) +export default UserModel diff --git a/server/types.ts b/server/types.ts new file mode 100644 index 0000000..b393d0f --- /dev/null +++ b/server/types.ts @@ -0,0 +1 @@ +export type authProviders = "facebook" | "twitter" | "instagram" | "google"