Skip to content

Commit

Permalink
feat: implement google oauth athorization
Browse files Browse the repository at this point in the history
  • Loading branch information
EvgenyWas committed May 5, 2024
1 parent dba6a21 commit 8f4f1b6
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 17 deletions.
8 changes: 8 additions & 0 deletions server/api/auth/login.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type { Profile as IProfile } from '~/types/user';
import { stringToBase64 } from '~/utils/converters';
import { emailValidator, passwordValidator } from '~/utils/validators';

const anotherAuthProviderMessage =
// eslint-disable-next-line max-len
'You tried signing in with a different authentication method than the one you used during signup. Please try again using your original authentication method.';

const loginPayloadSchema = z.object({
email: emailValidator,
password: passwordValidator,
Expand All @@ -28,6 +32,10 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 404, statusMessage: 'User with the provided email does not exist' });
}

if (user.auth_provider !== AUTH_PROVIDERS.Email_And_Password) {
throw createError({ statusCode: 404, statusMessage: anotherAuthProviderMessage });
}

if (user.password !== payload.password) {
throw createError({ statusCode: 403, statusMessage: 'Password is incorrect' });
}
Expand Down
35 changes: 26 additions & 9 deletions server/api/auth/token/refresh.post.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AUTH_PROVIDERS, COOKIE_NAMES } from '~/configs/properties';
import { userIdentitySchema } from '~/server/schemas';
import { jwtGenerator } from '~/server/services';
import octokitOAuthApp from '~/server/services/octokitOAuthApp';
import { googleOAuthClient, jwtGenerator, octokitOAuthApp } from '~/server/services';
import type { UserIdentity } from '~/server/types';
import { base64ToString } from '~/utils/converters';

Expand All @@ -11,10 +10,9 @@ export default defineEventHandler(async (event) => {
return sendError(event, createError({ statusCode: 400, statusMessage: 'Token is not provided' }));
}

const identityCookie = getCookie(event, COOKIE_NAMES.userIdentity) ?? '';
let identity: UserIdentity;
try {
identity = userIdentitySchema.parse(JSON.parse(base64ToString(identityCookie)));
identity = userIdentitySchema.parse(JSON.parse(base64ToString(getCookie(event, COOKIE_NAMES.userIdentity) ?? '')));
} catch (error) {
return sendError(
event,
Expand All @@ -38,14 +36,33 @@ export default defineEventHandler(async (event) => {
createError({ statusCode: 500, statusMessage: 'Internal server error during refreshing token' }),
);
}
} else {
}

if (identity.provider === AUTH_PROVIDERS.Google) {
try {
const { accessToken, refreshToken, refreshExpiresIn: maxAge } = jwtGenerator.refresh(token);
setCookie(event, COOKIE_NAMES.refreshToken, refreshToken, { httpOnly: true, sameSite: true, maxAge });
googleOAuthClient.setCredentials({ refresh_token: token });
const { credentials } = await googleOAuthClient.refreshAccessToken();
setCookie(event, COOKIE_NAMES.refreshToken, credentials.refresh_token ?? '', {
httpOnly: true,
sameSite: true,
maxAge: credentials.expiry_date as number,
});

return { accessToken, type: 'bearer' };
return { accessToken: credentials.access_token, type: credentials.token_type };
} catch (error) {
return sendError(event, createError({ statusCode: 401, data: error }));
return sendError(
event,
createError({ statusCode: 500, statusMessage: 'Internal server error during refreshing token' }),
);
}
}

try {
const { accessToken, refreshToken, refreshExpiresIn: maxAge } = jwtGenerator.refresh(token);
setCookie(event, COOKIE_NAMES.refreshToken, refreshToken, { httpOnly: true, sameSite: true, maxAge });

return { accessToken, type: 'bearer' };
} catch (error) {
return sendError(event, createError({ statusCode: 401, data: error }));
}
});
10 changes: 6 additions & 4 deletions server/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { AUTH_PROVIDERS, COOKIE_NAMES } from '~/configs/properties';
import { userIdentitySchema } from '~/server/schemas';
import { jwtGenerator } from '~/server/services';
import octokitOAuthApp from '~/server/services/octokitOAuthApp';
import { googleOAuthClient, jwtGenerator, octokitOAuthApp } from '~/server/services';
import type { UserIdentity } from '~/server/types';
import { base64ToString } from '~/utils/converters';

const AUTHORIZED_PATHES = ['/api/user'];

export default defineEventHandler(async (event) => {
if (AUTHORIZED_PATHES.some((path) => event.path.startsWith(path))) {
const identityCookie = getCookie(event, COOKIE_NAMES.userIdentity) ?? '';
let identity: UserIdentity;
try {
identity = userIdentitySchema.parse(JSON.parse(base64ToString(identityCookie)));
identity = userIdentitySchema.parse(
JSON.parse(base64ToString(getCookie(event, COOKIE_NAMES.userIdentity) ?? '')),
);
} catch (error) {
return sendError(
event,
Expand All @@ -26,6 +26,8 @@ export default defineEventHandler(async (event) => {
const token = accessToken.replace('Bearer ', '');
if (identity.provider === AUTH_PROVIDERS.Github) {
await octokitOAuthApp.checkToken({ token });
} else if (identity.provider === AUTH_PROVIDERS.Google) {
await googleOAuthClient.getTokenInfo(token);
} else {
jwtGenerator.verifyAccessToken(token);
}
Expand Down
57 changes: 57 additions & 0 deletions server/routes/google/oauth/callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AUTH_PROVIDERS, COOKIE_NAMES, USER_IDENTITY_MAX_AGE } from '~/configs/properties';
import Profile from '~/server/models/user/profile.model';
import { googleOAuthClient } from '~/server/services';
import type { GoogleUserInfoResponse } from '~/server/types';
import { stringToBase64 } from '~/utils/converters';

const anotherAuthProviderMessage =
// eslint-disable-next-line max-len
'You tried signing in with a different authentication method than the one you used during signup. Please try again using your original authentication method.';

export default defineEventHandler(async (event) => {
const queries = getQuery(event);
try {
if (queries.error) {
return sendError(event, createError({ statusCode: 400, statusMessage: queries.error as string }));
}

const { tokens } = await googleOAuthClient.getToken(queries.code as string);
const user = await $fetch<GoogleUserInfoResponse>('https://www.googleapis.com/userinfo/v2/me', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const existedProfile = await Profile.findOne({ email: user.email });

let profile = existedProfile;
if (existedProfile) {
if (existedProfile.auth_provider !== AUTH_PROVIDERS.Google) {
return sendError(event, createError({ statusCode: 403, statusMessage: anotherAuthProviderMessage }));
}
} else {
profile = await Profile.create({
name: user.name,
email: user.email,
avatar: user.picture,
auth_provider: AUTH_PROVIDERS.Google,
});
}

googleOAuthClient.setCredentials(tokens);

const indentity = stringToBase64(JSON.stringify({ id: profile?._id.toString(), provider: AUTH_PROVIDERS.Google }));

setCookie(event, COOKIE_NAMES.refreshToken, tokens.refresh_token ?? '', {
httpOnly: true,
sameSite: true,
maxAge: tokens.expiry_date as number,
});
setCookie(event, COOKIE_NAMES.userIdentity, indentity, {
httpOnly: true,
sameSite: true,
maxAge: USER_IDENTITY_MAX_AGE,
});

return await sendRedirect(event, '/', 302);
} catch (error) {
return sendError(event, createError({ statusCode: 500, statusMessage: JSON.stringify(error) }));
}
});
16 changes: 12 additions & 4 deletions server/routes/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { H3Event } from 'h3';

import { userIdentitySchema } from '../schemas';
import { AUTH_PROVIDERS, COOKIE_NAMES } from '~/configs/properties';
import octokitOAuthApp from '~/server/services/octokitOAuthApp';
import { googleOAuthClient, octokitOAuthApp } from '~/server/services';
import type { UserIdentity } from '~/server/types';
import { base64ToString } from '~/utils/converters';

Expand All @@ -27,12 +27,20 @@ export default defineEventHandler(async (event) => {
return await sendRedirect(event, location, 302);
}

const token = getCookie(event, COOKIE_NAMES.refreshToken) ?? '';
if (identity.provider === AUTH_PROVIDERS.Github) {
console.log('ACCESS TOKEN:', getCookie(event, COOKIE_NAMES.refreshToken));
try {
await octokitOAuthApp.deleteToken({ token: getCookie(event, COOKIE_NAMES.refreshToken) ?? '' });
await octokitOAuthApp.deleteToken({ token });
} catch (error) {
console.log('ERROR HELLO!', error);
console.log(error);
}
}

if (identity.provider === AUTH_PROVIDERS.Google) {
try {
await googleOAuthClient.revokeToken(token);
} catch (error) {
console.log(error);
}
}

Expand Down
11 changes: 11 additions & 0 deletions server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ export interface UserIdentity {
id: string;
provider: AUTH_PROVIDERS;
}

export interface GoogleUserInfoResponse {
id: string;
email: string;
verified_email: boolean;
name: string;
given_name: string;
family_name: string;
picture: string;
locale: string;
}

0 comments on commit 8f4f1b6

Please sign in to comment.