Module 4 Summary
This lesson is a summary of the work we've done in Module 4.0.
📝 This module's quiz can be found - here.
🗒️ Solutions for this module's quiz can be found - here.
In this module, we spent our efforts in creating the functionality to have a user log-in through Google Sign-In.
Server Project#
src/lib/api/Google.ts
#
In the src/lib/api/Google.ts
file of our server project, we created a Google
object instance that consolidates the functionality to interact with Google's servers. In the src/lib/api/Google.ts
file, we constructed an OAuth client with the help of the Google APIs Node.js Client. Within the Google
object we export from the file, there exist two properties:
authUrl
: Derives the authentication URL from Google's servers where users are directed to on the client to first sign-in with their Google account information.logIn()
: Function that uses Google's People API to get relevant information (i.e. their emails, names, and photos) for the Google account of a user.
import { google } from "googleapis";
const auth = new google.auth.OAuth2(
process.env.G_CLIENT_ID,
process.env.G_CLIENT_SECRET,
`${process.env.PUBLIC_URL}/login`
);
export const Google = {
authUrl: auth.generateAuthUrl({
// eslint-disable-next-line @typescript-eslint/camelcase
access_type: "online",
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
}),
logIn: async (code: string) => {
const { tokens } = await auth.getToken(code);
auth.setCredentials(tokens);
const { data } = await google.people({ version: "v1", auth }).people.get({
resourceName: "people/me",
personFields: "emailAddresses,names,photos"
});
return { user: data };
}
};
src/graphql/typeDefs.ts
#
We created three new root-level fields in our GraphQL API.
Query.authUrl
: Returns a string and is expected to return the Google Sign-In/OAuth authentication URL.Mutation.logIn
: Returns aViewer
object and is expected to log a viewer into the TinyHouse application.Mutation.logOut
: Returns aViewer
object and is expected to log a viewer out of the TinyHouse application.
import { gql } from "apollo-server-express";
export const typeDefs = gql`
type Viewer {
id: ID
token: String
avatar: String
hasWallet: Boolean
didRequest: Boolean!
}
input LogInInput {
code: String!
}
type Query {
authUrl: String!
}
type Mutation {
logIn(input: LogInInput): Viewer!
logOut: Viewer!
}
`;
src/graphql/resolvers/Viewer/index.ts
#
The resolver functions for the three new root-level fields in our API were established in a viewerResolvers
map kept within the src/lib/graphql/resolvers/Viewer/index.ts
file.
The authUrl()
resolver function simply returns the value of the authUrl
field in our Google
instance created in the src/lib/api/Google.ts
file.
The logIn()
resolver function receives a code
parameter from the input
argument provided to the logIn
mutation. A random token
is generated which will be used to help prevent CSRF. We then interact with Google's People API to derive the information of the user signing-in with Google. If the user is logging in to our TinyHouse application for the first time, we insert a new user document to the "users"
collection of our database. If the user already exists, we update the information of the relevant document in the "users"
collection.
The logOut()
resolver function simply returns a Viewer
object to the client with the didRequest
field set to true
to convey that the updated viewer information has been requested. At this moment, logOut()
doesn't achieve much. In the next module, we'll have logOut
involve removing persistent log-in sessions.
import crypto from "crypto";
import { IResolvers } from "apollo-server-express";
import { Google } from "../../../lib/api";
import { Viewer, Database, User } from "../../../lib/types";
import { LogInArgs } from "./types";
const logInViaGoogle = async (
code: string,
token: string,
db: Database
): Promise<User | undefined> => {
const { user } = await Google.logIn(code);
if (!user) {
throw new Error("Google login error");
}
// Name/Photo/Email Lists
const userNamesList = user.names && user.names.length ? user.names : null;
const userPhotosList = user.photos && user.photos.length ? user.photos : null;
const userEmailsList =
user.emailAddresses && user.emailAddresses.length
? user.emailAddresses
: null;
// User Display Name
const userName = userNamesList ? userNamesList[0].displayName : null;
// User Id
const userId =
userNamesList &&
userNamesList[0].metadata &&
userNamesList[0].metadata.source
? userNamesList[0].metadata.source.id
: null;
// User Avatar
const userAvatar =
userPhotosList && userPhotosList[0].url ? userPhotosList[0].url : null;
// User Email
const userEmail =
userEmailsList && userEmailsList[0].value ? userEmailsList[0].value : null;
if (!userId || !userName || !userAvatar || !userEmail) {
throw new Error("Google login error");
}
const updateRes = await db.users.findOneAndUpdate(
{ _id: userId },
{
$set: {
name: userName,
avatar: userAvatar,
contact: userEmail,
token
}
},
{ returnOriginal: false }
);
let viewer = updateRes.value;
if (!viewer) {
const insertResult = await db.users.insertOne({
_id: userId,
token,
name: userName,
avatar: userAvatar,
contact: userEmail,
income: 0,
bookings: [],
listings: []
});
This page is a preview of TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two