Skip to content

Commit

Permalink
feat: Add google auth for signin
Browse files Browse the repository at this point in the history
  • Loading branch information
GSinseswa721 committed May 5, 2024
1 parent b775d65 commit 347e17f
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 22 deletions.
13 changes: 0 additions & 13 deletions .env.example

This file was deleted.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"express-winston": "^4.2.0",
"highlight.js": "^11.9.0",
"jsend": "^1.1.0",
"morgan": "^1.10.0",
"nodemon": "^3.1.0",
"passport": "^0.7.0",
"passport-facebook": "^3.0.0",
"passport-google-oauth": "^2.0.0",
"passport-google-oauth20": "^2.0.0",
"pg": "^8.11.5",
"reflect-metadata": "^0.2.2",
"source-map-support": "^0.5.21",
Expand All @@ -53,10 +58,14 @@
"@types/eslint": "^8.56.10",
"@types/eslint__js": "^8.42.3",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"@types/jest": "^29.5.12",
"@types/jsend": "^1.0.32",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.7",
"@types/passport": "^1.0.16",
"@types/passport-facebook": "^3.0.3",
"@types/passport-google-oauth20": "^2.0.14",
"@types/reflect-metadata": "^0.1.0",
"@types/supertest": "^6.0.2",
"@types/winston": "^2.4.4",
Expand All @@ -75,4 +84,4 @@
"typescript": "^5.4.5",
"typescript-eslint": "^7.7.1"
}
}
}
2 changes: 1 addition & 1 deletion src/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Request, Response } from 'express';
import { User } from '../entities/User';;
import { User } from '../entities/User';
import bcrypt from 'bcrypt';
import { getRepository } from 'typeorm';

Expand Down
82 changes: 82 additions & 0 deletions src/controllers/facebook.auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { getRepository } from 'typeorm';
import { User } from '../entities/user.auth';

Check warning on line 2 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
import express from 'express';
import passport from 'passport';
// import session from 'express-session';
// import { PassportStatic } from 'passport';
const routerfb = express.Router();
require('dotenv').config();

Check failure on line 8 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Require statement not part of import statement

async function findUserAccountIdProvider(accountId: string, provider: string): Promise<User | undefined> {

Check warning on line 10 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Missing space before function parentheses
try {
const userRepository = getRepository(User);

Check warning on line 13 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
const user = await userRepository.findOne({
where: {
accountId: accountId,
provider: provider,
},
});

Check warning on line 20 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
if (user === null) {
return undefined;
}

Check warning on line 24 in src/controllers/facebook.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
return user as User;
} catch (error) {
console.error('Error finding user:', error);
return undefined;
}
}
const accountId = 'your_account_id';
const provider = 'facebook';

findUserAccountIdProvider(accountId, provider)
.then((user) => {
if (user) {
console.log('User found:', user);
} else {
console.log('User not found');
}
})
.catch((error) => {
console.error('Error:', error);
});

routerfb.get('/', passport.authenticate('facebook', { scope: 'email' }));

routerfb.get(
'/callback',
passport.authenticate('facebook', {
failureRedirect: '/auth/facebook/error',
}),
function (req, res) {
// Successful authentication, redirect to success screen.
res.redirect('/auth/facebook/success');
}
);

routerfb.get('/success', async (req, res) => {
const userInfo = {
id: req.session.passport.user.id,
displayName: req.session.passport.user.displayName,
provider: req.session.passport.user.provider,
};
res.render('fb-github-success', { user: userInfo });
});

routerfb.get('/error', (req, res) => res.send('Error logging in via Facebook..'));

routerfb.get('/signout', (req, res) => {
try {
req.session.destroy(function (err) {
console.log('session destroyed.');
});
res.render('auth');
} catch (err) {
res.status(400).send({ message: 'Failed to sign out fb user' });
}
});

module.exports = routerfb;

49 changes: 49 additions & 0 deletions src/controllers/google.auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import passport from 'passport';
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

Check failure on line 2 in src/controllers/google.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Require statement not part of import statement
import express from 'express';
import googleAuth from '../middlewares/auth';
const routers = express.Router();
require('dotenv').config();

Check failure on line 6 in src/controllers/google.auth.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Require statement not part of import statement

let userProfile: any;
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.CALLBACK_URL,
},
function (_accessToken: any, _refreshToken: any, profile: any, done: (arg0: null, arg1: any) => any) {
userProfile = profile;
return done(null, userProfile);
}
)
);

// request at /auth/google, when user click sign-up with google button transferring
// the request to google server, to show emails screen
routers.get(
'/',
passport.authenticate('google', { scope: ['profile', 'email'] })
);

// URL Must be same as 'Authorized redirect URIs' field of OAuth client, i.e: /auth/google/callback
routers.get(
'/callback',
passport.authenticate('google', { failureRedirect: '/auth/google/error' }),
(req: any, res: { redirect: (arg0: string) => void; }) => {
res.redirect('/auth/google/success'); // Successful authentication, redirect success.
}
);

routers.get('/success', async (req: any, res: { render: (arg0: string, arg1: { user: any; }) => void; }) => {
const { failure, success } = await googleAuth.registerWithGoogle(userProfile);
if (failure) console.log('Google user already exist in DB..');
else console.log('Registering new Google user..');
res.render('success', { user: userProfile });
});

routers.get('/error', (req: any, res: { send: (arg0: string) => any; }) => res.send('Error logging in via Google..'));


export default routers;
25 changes: 25 additions & 0 deletions src/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import {
@Entity()
@Unique(['email'])
export class User {
save() {
throw new Error('Method not implemented.');
}
static create(arg0: { accountId: string; name: string; provider: string; }): User | PromiseLike<User | null> | null {
throw new Error('Method not implemented.');
}
static findOne(arg0: { accountId: any; provider: string; }) {
throw new Error('Method not implemented.');
}
@PrimaryGeneratedColumn('uuid')
@IsNotEmpty()
id!: string;
Expand Down Expand Up @@ -66,4 +75,20 @@ import {

@UpdateDateColumn()
updatedAt!: Date;

@Column({ nullable: true })
@IsString()
accountId?: string;

@Column({ nullable: true })
@IsString()
name?: string;

@Column({ nullable: true })
@IsString()
photoURL?: string;

@Column({ nullable: true })
@IsString()
provider?: string;
}
22 changes: 22 additions & 0 deletions src/entities/user.auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';

@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string | undefined;

@Column({ type: 'text', nullable: true, unique: true })
email: string | undefined;

@Column({ type: 'text', nullable: true })
accountId: string | undefined;

@Column({ type: 'text', nullable: true })
name!: string;

@Column({ type: 'text', nullable: true })
photoURL: string | undefined;

@Column({ type: 'text', nullable: true })
provider: string | undefined;
}
17 changes: 13 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import express, { Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import router from './routes';
import { addDocumentation } from './startups/docs';
import 'reflect-metadata';


import dotenv from 'dotenv';
dotenv.config();
import { CustomError, errorHandler } from './middlewares/errorHandler';
import morgan from 'morgan';
import { dbConnection } from './startups/dbConnection';
import passport from 'passport';
import session from 'express-session';
import routers from '../src/controllers/google.auth';
dotenv.config();

export const app = express();
Expand All @@ -32,6 +34,13 @@ dbConnection();
const morganFormat = ':method :url :status :response-time ms - :res[content-length]';
app.use(morgan(morganFormat));


//Google OAuth routes
app.use('/auth/google', routers);
export const server = app.listen(port, () => {
console.log(`[server]: Server is running at http://localhost:${port}`);
});
});

function passportConfig() {
throw new Error('Function not implemented.');
}
72 changes: 72 additions & 0 deletions src/middlewares/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { User } from '../entities/user.auth';
import { getRepository, FindOneOptions } from 'typeorm';

interface OAuthUser {
id: any;
provider: any;
displayName: any;
emails: { value: any }[];
photos: { value: any }[];
}

const googleAuthDal = {
registerWithGoogle: async (oauthUser: OAuthUser) => {
const userRepository = getRepository(User);

try {
// Check if user already exists
const existingUser = await userRepository.findOne({
where: {
accountId: oauthUser.id,
provider: oauthUser.provider,
},
});

if (existingUser) {
return { failure: { message: 'User already registered.' } };
}

// Create a new user entity
const newUser = userRepository.create({
accountId: oauthUser.id,
name: oauthUser.displayName,
provider: oauthUser.provider,
// Adjust property names based on your User entity
email: oauthUser.emails[0]?.value, // Assuming emails[0] contains the email value
photoURL: oauthUser.photos[0]?.value, // Assuming photos[0] contains the photo URL value
});

// Save the new user to the database
await userRepository.save(newUser);

return { success: { message: 'User registered successfully.' } };
} catch (error) {
console.error('Error registering user with Google:', error);
return { failure: { message: 'Failed to register user.' } };
}
},

loginUser: async (oauthUser: OAuthUser) => {
const userRepository = getRepository(User);

try {
// Check if a user with the provided email exists
const userExists = await userRepository.findOne({
where: {
email: oauthUser.emails[0]?.value, // Assuming emails[0] contains the email value
},
});

if (userExists) {
return { success: { message: 'User successfully logged in.' } };
} else {
return { failure: { message: 'Email not registered. You need to sign up first.' } };
}
} catch (error) {
console.error('Error logging in user:', error);
return { failure: { message: 'Failed to log in user.' } };
}
},
};

export default googleAuthDal;
8 changes: 8 additions & 0 deletions src/types/session.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'express-session';
import { PassportStatic } from 'passport';

declare module 'express-session' {
interface SessionData {
passport?: any; // Define the passport property as any
}
}
8 changes: 5 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": {
"*": ["types/*"]
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
"types": ["node", "jest"] /* Specify type package names to be included without being referenced in a source file. */,
Expand Down Expand Up @@ -98,6 +100,6 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "types"],
"exclude": ["node_modules"]
}

0 comments on commit 347e17f

Please sign in to comment.