Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 125 additions & 34 deletions src/lib/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { ApiClient, User } from './apiClient.service';
import type { ApiClient, User, UserUpdate } from "./apiClient.service";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frostbournesb @saito-sv @ocasta181 Let's agree on this and we can set the linter on all of the projects. Single or double quotes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say single is easier to type but do not have a strong preference

import type { LocationService, VisitorData } from './location.service';

export function createAuthService({ apiClient, locationService, bypassDeviceCheck }: AuthServiceParams): AuthService {
const previousAttempt = { signature: "", nonce: "" };
let emailCheckInterval: NodeJS.Timer | undefined;
let deviceCheckInterval: NodeJS.Timer | undefined;
const previousAttempt = { signature: "", nonce: "" };

const login = async (nonce: string, signature: string, visitorData?: VisitorData) => {
const data = await apiClient.loginUser(nonce, signature, visitorData, bypassDeviceCheck);
return data;
};

const loginOrCreateUser = async (walletAddress: string) => {
const authorizeUser = async (walletAddress: string) => {
const { nonce } = await apiClient.requestLogin(walletAddress);
const signature = await requestSignature(walletAddress, nonce);
const visitorData = await locationService.getVisitorData();


// is there a better way to do this? I'm concerned about keeping this state in memory and not in local storage
previousAttempt.nonce = nonce;
Expand All @@ -23,20 +21,12 @@ export function createAuthService({ apiClient, locationService, bypassDeviceChec
return data;
} catch (err: any) {
// if user already exists, try to login
if (err.code === "CONFLICT") return login(nonce, signature, visitorData);
if (err.code === "CONFLICT") return apiClient.loginUser(nonce, signature, visitorData, bypassDeviceCheck);
throw err;
}
};

const logout = async () => {
try {
await apiClient.logoutUser();
} catch {
return;
}
}

const getPreviousLogin = async () => {
const getPreviousSignature = async () => {
// TODO: Use refresh token instead
// TODO: Modify refresh token endpoint to verify visitor data
if (!previousAttempt.signature) throw { code: "UNAUTHORIZED" };
Expand Down Expand Up @@ -64,8 +54,9 @@ export function createAuthService({ apiClient, locationService, bypassDeviceChec
});

return signature;
} catch (error) {
console.log("SDK :: Wallet signature error: ", error);
} catch (err: any) {
console.log("SDK :: Wallet signature error: ", err);
throw err;
}
}

Expand All @@ -74,29 +65,129 @@ export function createAuthService({ apiClient, locationService, bypassDeviceChec
const { user } = await apiClient.refreshToken(walletAddress);
return user;
} catch (err: any) {
return null;
throw err;
}
}

const emailVerification = async (userId: string, email: string) => {
try {
await apiClient.requestEmailVerification(userId, email);

clearInterval(emailCheckInterval);
emailCheckInterval = setInterval(async () => {
const { status } = await apiClient.getUserStatus(userId);
if (status == "email_verified") {
clearInterval(emailCheckInterval);
return status
}
}, 5000);
} catch (err: any){
throw err
}
}

const emailPreview = async (walletAddress: string) => {
try {
let nonce: string;
let signature: string;

const previous = await getPreviousSignature();
nonce = previous.nonce;
signature = previous.signature;

if (!previous.signature) {
nonce = (await apiClient.requestLogin(walletAddress)).nonce;
signature = await requestSignature(walletAddress, nonce);
}

const { email } = await apiClient.getUserEmailPreview(nonce, signature);
return email;

}
catch (err: any) {
throw err
}
}

const deviceVerification = async (walletAddress: string) => {
try {
let nonce: string;
let signature: string;

const previous = await getPreviousSignature();
nonce = previous.nonce;
signature = previous.signature;

if (!previous.signature) {
nonce = (await apiClient.requestLogin(walletAddress)).nonce;
signature = await requestSignature(walletAddress, nonce);
}

await apiClient.requestDeviceVerification(nonce, signature, previous.visitor);

clearInterval(deviceCheckInterval);
deviceCheckInterval = setInterval(async () => {
const { status } = await apiClient.getDeviceStatus(nonce, signature, previous.visitor);
if (status == "verified") {
clearInterval(deviceCheckInterval);
return status
}
}, 5000);
} catch (err: any) {
throw err;
}
}

const updateUser = async (userId: string, userUpdate: UserUpdate) => {
try {
return await apiClient.updateUser(userId, userUpdate);
} catch (err: any) {
throw err;
}
}

const logout = async () => {
try {
await apiClient.logoutUser();
} catch (err: any) {
throw err;
}
}

const cleanup = () => {
clearInterval(emailCheckInterval);
clearInterval(deviceCheckInterval);
}

return {
loginOrCreateUser,
fetchLoggedInUser,
authorizeUser,
emailVerification,
emailPreview,
deviceVerification,
fetchLoggedInUser,
requestSignature,
getPreviousLogin,
logout
};
getPreviousSignature,
updateUser,
logout,
cleanup
};
}

export interface AuthService {
authorizeUser: (walletAddress: string) => Promise<any>;
fetchLoggedInUser: (walletAddress: string) => Promise<User | null>;
requestSignature: (userAddress: string, encodedMessage: string) => Promise<string>;
getPreviousSignature: () => Promise<{nonce: string, signature: string, visitor: VisitorData}>;
emailVerification: (userId: string, email: string) => Promise<any>;
emailPreview: (walletAddress: string) => Promise<any>;
deviceVerification: (walletAddress: string) => Promise<any>;
updateUser: (userId: string, userUpdate: UserUpdate) => Promise<User>;
logout: () => Promise<void>;
cleanup: () => void;
}

export interface AuthServiceParams {
apiClient: ApiClient;
locationService: LocationService;
bypassDeviceCheck: boolean;
}

export interface AuthService {
loginOrCreateUser: (walletAddress: string) => Promise<{ user: User }>;
fetchLoggedInUser: (walletAddress: string) => Promise<User | null>;
requestSignature: (userAddress: string, encodedMessage: string) => Promise<string>;
getPreviousLogin: () => Promise<{nonce: string, signature: string, visitor: VisitorData}>;
logout: () => Promise<any>;
}
74 changes: 13 additions & 61 deletions src/lib/services/events.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ export enum Events {
const eventHandlers: Record<string, (event: StringEvent, stringPay: StringPay) => void> = {};

export function createEventsService(iframeUrl: string, authService: AuthService, quoteService: QuoteService, apiClient: ApiClient, locationService: LocationService) {
let emailCheckInterval: NodeJS.Timer | undefined;
let deviceCheckInterval: NodeJS.Timer | undefined;

const sendEvent = <T = any>(frame: HTMLIFrameElement, eventName: string, data?: T, error?: any) => {
if (!frame) {
err("a frame was not provided to sendEvent");
Expand Down Expand Up @@ -79,9 +76,8 @@ export function createEventsService(iframeUrl: string, authService: AuthService,

function cleanup() {
unregisterEvents();

clearInterval(emailCheckInterval);
clearInterval(deviceCheckInterval);
authService.cleanup();


const stringPay = window.StringPay;

Expand Down Expand Up @@ -152,8 +148,9 @@ export function createEventsService(iframeUrl: string, authService: AuthService,
if (!stringPay.frame || !stringPay.payload) throw new Error("Iframe not ready");

try {
const { user } = await authService.loginOrCreateUser(stringPay.payload.userAddress);
const { user } = await authService.authorizeUser(stringPay.payload.userAddress);
sendEvent(stringPay.frame, Events.RECEIVE_AUTHORIZE_USER, { user });
return;
} catch (error: any) {
console.debug("SDK :: onAuthorizeUser error: ", error);
sendEvent(stringPay.frame, Events.RECEIVE_AUTHORIZE_USER, {}, error);
Expand All @@ -165,9 +162,9 @@ export function createEventsService(iframeUrl: string, authService: AuthService,

try {
const data = <{ userId: string, update: UserUpdate }>event.data;
const user = await apiClient.updateUser(data.userId, data.update);

const user = await authService.updateUser(data.userId, data.update);
sendEvent(frame, Events.RECEIVE_UPDATE_USER, { user });
return;
} catch (error: any) {
sendEvent(frame, Events.RECEIVE_UPDATE_USER, {}, error);
}
Expand All @@ -178,52 +175,20 @@ export function createEventsService(iframeUrl: string, authService: AuthService,

try {
const data = <{ userId: string; email: string }>event.data;

await apiClient.requestEmailVerification(data.userId, data.email);

clearInterval(emailCheckInterval);
emailCheckInterval = setInterval(async () => {
const { status } = await apiClient.getUserStatus(data.userId);
if (status == "email_verified") {
sendEvent(frame, Events.RECEIVE_EMAIL_VERIFICATION, { status });
clearInterval(emailCheckInterval);
}
}, 5000);
const status = await authService.emailVerification(data.userId, data.email);
sendEvent(frame, Events.RECEIVE_EMAIL_VERIFICATION, { status });
} catch (error: any) {
sendEvent(frame, Events.RECEIVE_EMAIL_VERIFICATION, {}, error);
}
}

async function onDeviceVerification(event: StringEvent, { frame }: StringPay) {
if (!frame) throw new Error("Iframe not ready");

try {
const data = <{ walletAddress: string }>event.data;

let nonce: string;
let signature: string;

const previous = await authService.getPreviousLogin();
nonce = previous.nonce;
signature = previous.signature;

const visitor = previous.visitor;

if (!previous.signature) {
nonce = (await apiClient.requestLogin(data.walletAddress)).nonce;
signature = await authService.requestSignature(data.walletAddress, nonce);
}

await apiClient.requestDeviceVerification(nonce, signature, visitor);

clearInterval(deviceCheckInterval);
deviceCheckInterval = setInterval(async () => {
const { status } = await apiClient.getDeviceStatus(nonce, signature, visitor);
if (status == "verified") {
sendEvent(frame, Events.RECEIVE_DEVICE_VERIFICATION, { status });
clearInterval(deviceCheckInterval);
}
}, 5000);
const status = await authService.deviceVerification(data.walletAddress);
sendEvent(frame, Events.RECEIVE_DEVICE_VERIFICATION, { status });
return;
} catch (error: any) {
sendEvent(frame, Events.RECEIVE_DEVICE_VERIFICATION, {}, error);
}
Expand All @@ -234,22 +199,9 @@ export function createEventsService(iframeUrl: string, authService: AuthService,

try {
const data = <{ walletAddress: string }>event.data;

let nonce: string;
let signature: string;

const previous = await authService.getPreviousLogin();
nonce = previous.nonce;
signature = previous.signature;

if (!previous.signature) {
nonce = (await apiClient.requestLogin(data.walletAddress)).nonce;
signature = await authService.requestSignature(data.walletAddress, nonce);
}

const { email } = await apiClient.getUserEmailPreview(nonce, signature);

const email = await authService.emailPreview(data.walletAddress);
sendEvent(frame, Events.RECEIVE_EMAIL_PREVIEW, { email });
return;
} catch (error: any) {
sendEvent(frame, Events.RECEIVE_EMAIL_PREVIEW, {}, error);
}
Expand Down