Skip to content

Commit

Permalink
refactor(iam): SignInWithOAuthProvider -> SignInWithGoogle
Browse files Browse the repository at this point in the history
  • Loading branch information
Gi-jutsu committed Aug 18, 2024
1 parent 39bc5c7 commit 7acd244
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 74 deletions.
10 changes: 5 additions & 5 deletions src/identity-and-access/identity-and-access.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import {
} from "@identity-and-access/infrastructure/services/jwt.service.js";
import { ReadMeHttpController } from "@identity-and-access/use-cases/read-me/http.controller.js";
import { ReadMeUseCase } from "@identity-and-access/use-cases/read-me/use-case.js";
import { SignInWithOAuthProviderHttpController } from "@identity-and-access/use-cases/sign-in-with-oauth-provider/http.controller.js";
import { SignInWithOAuthProviderUseCase } from "@identity-and-access/use-cases/sign-in-with-oauth-provider/use-case.js";
import { SignInWithGoogleHttpController } from "@identity-and-access/use-cases/sign-in-with-google/http.controller.js";
import { SignInWithGoogleUseCase } from "@identity-and-access/use-cases/sign-in-with-google/use-case.js";
import { Module } from "@nestjs/common";

@Module({
controllers: [
/** HTTP Controllers */
ReadMeHttpController,
SignInWithOAuthProviderHttpController,
SignInWithGoogleHttpController,
],
providers: [
/** Infrastructure / Services */
Expand All @@ -24,8 +24,8 @@ import { Module } from "@nestjs/common";
/** Use Cases */
ReadMeUseCase,
{
provide: SignInWithOAuthProviderUseCase,
useFactory: (jwt: JwtService) => new SignInWithOAuthProviderUseCase(jwt),
provide: SignInWithGoogleUseCase,
useFactory: (jwt: JwtService) => new SignInWithGoogleUseCase(jwt),
inject: [JwtServiceToken],
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";

describe("SignInWithGoogleUseCase", () => {
it("should redirect to the google authorization URL", async () => {
// Act
const response = await fetch(
`http://localhost:8080/identity-and-access/oauth/google`
);

// Assert
expect(response.redirected).toBe(true);
expect(response.url).toMatch(
"https://accounts.google.com/v3/signin/identifier"
);
});

describe("when successfully signed in with Google", () => {
it("should set the jwt access token in a secure, HTTP-only, SameSite=Strict cookie", async () => {
// Act
const response = await fetch(
"http://localhost:8080/identity-and-access/oauth/google/callback"
);

// Assert
const expectedSetCookie = new RegExp(
"access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[A-Za-z0-9-_]+.[A-Za-z0-9-_]+; Path=/; HttpOnly; Secure; SameSite=Strict"
);

expect(response.headers.get("set-cookie")).toMatch(expectedSetCookie);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import {
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import type { Response } from "express";
import { SignInWithOAuthProviderUseCase } from "./use-case.js";
import { SignInWithGoogleUseCase } from "./use-case.js";

@Controller()
export class SignInWithOAuthProviderHttpController {
export class SignInWithGoogleHttpController {
constructor(
private readonly config: ConfigService,
private readonly useCase: SignInWithOAuthProviderUseCase
private readonly useCase: SignInWithGoogleUseCase
) {}

@Get("/identity-and-access/oauth/:provider")
Expand Down Expand Up @@ -46,6 +46,7 @@ export class SignInWithOAuthProviderHttpController {
@Get("/identity-and-access/oauth/:provider/callback")
async exchangeCodeForTokens(
@Param("provider") provider: string,
@Param("code") code: string,
@Res() response: Response
) {
// @TODO: Handle more OAuth providers (e.g. Google, Facebook, GitHub)
Expand All @@ -54,10 +55,7 @@ export class SignInWithOAuthProviderHttpController {
}

try {
const { accessToken } = await this.useCase.execute({
code: "<your_code>",
provider,
});
const { accessToken } = await this.useCase.execute({ code });

response.cookie("access_token", accessToken, {
httpOnly: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import { SignInWithGoogleUseCase } from "./use-case.js";

describe("SignInWithGoogleUseCase", () => {
const fakeJwtService = { sign: () => "fakeJwtToken" };
const useCase = new SignInWithGoogleUseCase(fakeJwtService);

it.todo("should exchange the authorization code for an access token");

it("should return an access token", async () => {
// Act
const output = await useCase.execute({
code: "authorizationCode",
});

// Assert
expect(output).toEqual({
accessToken: "fakeJwtToken",
});
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { JwtService } from "@identity-and-access/infrastructure/services/jwt.service.js";

/** @TODO: SignInWithOAuthProviderUseCase should be responsible for
/** @TODO: SignInWithGoogleUseCase should be responsible for
* - exchanging the code for tokens
* - generating the access and refresh token (RFC 9068)
*/
export class SignInWithOAuthProviderUseCase {
export class SignInWithGoogleUseCase {
constructor(private readonly jwt: JwtService) {}

async execute(command: SignInWithOAuthProviderCommand) {
async execute(command: SignInWithGoogleCommand) {
return {
accessToken: this.jwt.sign(
{
Expand All @@ -17,14 +17,15 @@ export class SignInWithOAuthProviderUseCase {
sub: "fake_user_id",
iat: Math.floor(Date.now() / 1000), // now
},
// @TODO: Use an environment variable for the secret
"secret"
),
};
}
}

export type SignInWithOAuthProviderCommand = {
export type SignInWithGoogleCommand = {
// The code received from the Google OAuth callback
// @see https://developers.google.com/identity/protocols/oauth2/web-server
code: string;
// @TODO: Add more OAuth providers (e.g. Google, Facebook, GitHub)
provider: "google";
};

This file was deleted.

This file was deleted.

0 comments on commit 7acd244

Please sign in to comment.