Skip to content

Commit

Permalink
feat: create googleAuthCallback
Browse files Browse the repository at this point in the history
  • Loading branch information
vighnesh153 committed Nov 24, 2024
1 parent d2c3abd commit 81b1bf1
Show file tree
Hide file tree
Showing 19 changed files with 423 additions and 38 deletions.
7 changes: 5 additions & 2 deletions tools-deno/api-vighnesh153/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
},
"imports": {
"@/": "./src/",
"@std/encoding": "jsr:@std/encoding@^1.0.5",
"@std/assert": "jsr:@std/assert@^1.0.8",
"@std/collections": "jsr:@std/collections@^1.0.9",
"@std/text": "jsr:@std/text@^1.0.8",
"@vighnesh153/tools": "jsr:@vighnesh153/[email protected]",
"firebase-admin": "npm:firebase-admin@^13.0.1",
"hono": "jsr:@hono/[email protected]"
"hono": "jsr:@hono/[email protected]",
"zod": "npm:zod@^3.23.8"
},
"nodeModulesDir": "auto"
}
20 changes: 14 additions & 6 deletions tools-deno/api-vighnesh153/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { JsonHttpPostRequest } from "@vighnesh153/tools";
import { config } from "@/config.ts";
import { serverAuthRedirectUrl } from "@/constants.ts";

export function buildTokenFetchRequest(
{ authCallbackCode }: { authCallbackCode: string },
): JsonHttpPostRequest<FormData> {
const formData = new FormData();

formData.append("code", authCallbackCode);
formData.append("client_id", config.googleClientId);
formData.append("client_secret", config.googleClientSecret);
formData.append("grant_type", "authorization_code");
formData.append("redirect_uri", serverAuthRedirectUrl);

return {
path: "https://oauth2.googleapis.com/token",
data: formData,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { JsonHttpClient, JsonHttpClientImpl, not } from "@vighnesh153/tools";
import { decodeUserInfo } from "./decode_user_info.ts";
import { UserRepository } from "@/repositories/mod.ts";
import {
authTokenGeneratorFactory,
userRepositoryFactory,
} from "@/factories/mod.ts";
import { AuthTokenGenerator } from "@/utils/auth_token_generator.ts";
import { CompleteUserInfo } from "@/models/user_info.ts";
import { buildTokenFetchRequest } from "./build_token_fetch_request.ts";

type ControllerResponse = { success: false } | {
success: true;
user: CompleteUserInfo;
authToken: string;
};

export async function googleAuthCallbackController(
{
authCallbackCode = "",
httpClient = new JsonHttpClientImpl({ baseUrl: "" }),
userRepository = userRepositoryFactory(),
authTokenGenerator = authTokenGeneratorFactory(),
}: {
authCallbackCode?: string;
httpClient?: JsonHttpClient;
userRepository?: UserRepository;
authTokenGenerator?: AuthTokenGenerator;
} = {},
): Promise<ControllerResponse> {
const tokenFetchRequest = buildTokenFetchRequest({ authCallbackCode });

const tokenFetcher = httpClient.post<unknown, { id_token: string }>(
tokenFetchRequest,
);

console.log("Fetching google auth token...");
const tokenResponse = await tokenFetcher.execute();

if (tokenResponse.isError()) {
console.log("Some error occurred while fetching google auth token");
console.log(tokenResponse.getErrorResponse());
return {
success: false,
};
}

console.log("Google auth token fetch is successful");

const tokenData = tokenResponse.getSuccessResponse();

// extract user info from token
console.log("Extracting user info from token");
const oauthUserInfo = decodeUserInfo(tokenData.data.id_token);

if (oauthUserInfo === null) {
console.log("Failed to extract user info from token");
console.log(`token=${tokenData.data.id_token}`);
return { success: false };
}

console.log("Successfully extracted user info from token");

// user's email is not verified. deny signing in
if (not(oauthUserInfo.email_verified)) {
console.log(`User's email address is not verified`);
console.log(oauthUserInfo);
return { success: false };
}

console.log(`User's email address is verified`);

console.log("Attempting to creating or getting user...");
const loggedInUser = await userRepository.createOrGetUser(oauthUserInfo);

if (loggedInUser == null) {
console.log(
"Failed to create or get user... Failing sign up or sign in...",
);
return { success: false };
}

console.log("Generating auth token...");
const authToken = authTokenGenerator.generate({
userId: loggedInUser.userId,
});

console.log("Generated auth token and login user complete...");

return {
success: true,
user: loggedInUser,
authToken,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GoogleOAuthUserInfo } from "@/models/user_info.ts";

export function decodeUserInfo(token: string): GoogleOAuthUserInfo | null {
try {
const json = atob(token.split(".")[1]);
const parsedResult = GoogleOAuthUserInfo.safeParse(JSON.parse(json));
if (parsedResult.success) {
return parsedResult.data;
}
throw parsedResult.error;
} catch (e) {
console.log(`Some error occurred while parsing Google Oauth token`);
console.log(e);
return null;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { serverAuthRedirectUrl } from "@/constants.ts";
import { constructInitiateGoogleAuthUrl } from "./construct_initiate_google_auth_url.ts";

export function initiateGoogleLoginController(): string | null {
return constructInitiateGoogleAuthUrl({
authRedirectUri: serverAuthRedirectUrl,
});
}
11 changes: 4 additions & 7 deletions tools-deno/api-vighnesh153/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { decodeBase64 } from "@std/encoding";

const textDecoder = new TextDecoder();

function getEnvVar(key: string): string {
const variable = Deno.env.get(key);
if (!variable) {
Expand All @@ -13,7 +9,8 @@ function getEnvVar(key: string): string {
export const config = {
googleClientId: getEnvVar("GOOGLE_CLIENT_ID"),
googleClientSecret: getEnvVar("GOOGLE_CLIENT_SECRET"),
firebaseConfig: JSON.parse(textDecoder.decode(decodeBase64(
getEnvVar("FIREBASE_SERVICE_ACCOUNT_CONFIG_B64"),
))),
cookieSecret: getEnvVar("COOKIE_SECRET"),
firebaseConfig: JSON.parse(
atob(getEnvVar("FIREBASE_SERVICE_ACCOUNT_CONFIG_B64")),
),
};
2 changes: 2 additions & 0 deletions tools-deno/api-vighnesh153/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const apiHost = apiHosts[key];
export const apiDomain = apiDomains[key];
export const uiHost = uiHosts[key];
export const uiDomain = uiDomains[key];
export const serverAuthRedirectUrl = `${apiDomain}/googleAuthCallback`;
export const uiAuthCompleteUrl = `${uiDomain}/auth/callback`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createSingletonFactory } from "@vighnesh153/tools";
import {
AuthTokenGenerator,
AuthTokenGeneratorImpl,
} from "@/utils/auth_token_generator.ts";

export const authTokenGeneratorFactory = createSingletonFactory<
AuthTokenGenerator
>(() => new AuthTokenGeneratorImpl());
2 changes: 2 additions & 0 deletions tools-deno/api-vighnesh153/src/factories/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./auth_token_generator_factory.ts";
export * from "./user_repository_factory.ts";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createSingletonFactory } from "@vighnesh153/tools";
import { FirebaseUserRepository, UserRepository } from "@/repositories/mod.ts";

export const userRepositoryFactory = createSingletonFactory<UserRepository>(
() => new FirebaseUserRepository(),
);
33 changes: 33 additions & 0 deletions tools-deno/api-vighnesh153/src/models/user_info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { z } from "zod";
import { filterKeys } from "@std/collections";

export const GoogleOAuthUserInfo = z.object({
name: z.string().min(1),
email: z.string().email(),
picture: z.string().min(1),
email_verified: z.boolean(),
});
export type GoogleOAuthUserInfo = z.infer<typeof GoogleOAuthUserInfo>;

export const PublicUserInfo = z.object({
userId: z.string().min(1),
username: z.string().min(1),
name: z.string().min(1),
profilePictureUrl: z.string().min(1),
createdAtMillis: z.number(),
});
export type PublicUserInfo = z.infer<typeof PublicUserInfo>;

export const CompleteUserInfo = PublicUserInfo.extend({
email: z.string().email(),
});
export type CompleteUserInfo = z.infer<typeof CompleteUserInfo>;

export function convertToPublicUserInfo(
completeUserInfo: CompleteUserInfo,
): PublicUserInfo {
return filterKeys(
completeUserInfo,
(field) => field != "email",
) as PublicUserInfo;
}
1 change: 1 addition & 0 deletions tools-deno/api-vighnesh153/src/repositories/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./user_repository.ts";
Loading

0 comments on commit 81b1bf1

Please sign in to comment.