Skip to content

Commit

Permalink
feat: authentication api
Browse files Browse the repository at this point in the history
  • Loading branch information
vitoUwu committed Feb 26, 2025
1 parent 178ac70 commit 1bb14c1
Show file tree
Hide file tree
Showing 11 changed files with 792 additions and 48 deletions.
16 changes: 16 additions & 0 deletions utils/safeParse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @description Safely parses a value to a JSON object. It doesn't validates the object, it just tries to parse it.
* @param value - The value to parse
* @returns The parsed value or null if the value is not a string
*/
export default function safeParse<T>(value: string): T | null {
if (typeof value !== "string") {
return null;
}

try {
return JSON.parse(value) as T;
} catch (_e) {
return null;
}
}
100 changes: 100 additions & 0 deletions vtex/actions/authentication/accessKeySignIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { getCookies } from "std/http/cookie.ts";
import { setCookie } from "std/http/mod.ts";
import safeParse from "../../../utils/safeParse.ts";
import { AppContext } from "../../mod.ts";
import { AuthResponse } from "../../utils/types.ts";

export interface Props {
email: string;
accessKey: string;
}

/**
* @title VTEX Integration - Authenticate with Email and AcessKey
* @description Return authStatus that show if user is logged or something wrong happens.
*/
export default async function action(
props: Props,
req: Request,
ctx: AppContext,
): Promise<AuthResponse | null> {
const { vcsDeprecated } = ctx;

if (!props.email || !props.accessKey) {
console.error("Email and/or password is missing:", props);
return null;
}

try {
const cookies = getCookies(req.headers);
const VtexSessionToken = cookies?.["VtexSessionToken"] ?? null;

if (!VtexSessionToken) {
console.error("VtexSessionToken cookie is missing", VtexSessionToken);
return null;
}

const body = new URLSearchParams();
body.append("login", props.email);
body.append("accessKey", props.accessKey);
body.append("authenticationToken", VtexSessionToken);

const response = await vcsDeprecated
["POST /api/vtexid/pub/authentication/accesskey/validate"](
{},
{
body,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
},
);

if (!response.ok) {
console.error(
"Authentication request failed",
response.status,
response.statusText,
);
return null;
}

const data: AuthResponse = await response.json();
if (data.authStatus === "Success") {
const VTEXID_EXPIRES = data.expiresIn;

if (data.authCookie) {
setCookie(ctx.response.headers, {
name: data.authCookie.Name,
value: data.authCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}

if (data.accountAuthCookie) {
setCookie(ctx.response.headers, {
name: data.accountAuthCookie.Name,
value: data.accountAuthCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}
}

await ctx.invoke.vtex.actions.session.editSession({});

return data;
} catch (error) {
console.error("Unexpected error during authentication", error);
if (error instanceof Error) {
return safeParse<AuthResponse>(error.message);
}
return null;
}
}
103 changes: 103 additions & 0 deletions vtex/actions/authentication/classicSignIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { setCookie } from "std/http/mod.ts";
import safeParse from "../../../utils/safeParse.ts";
import { AppContext } from "../../mod.ts";
import { AuthResponse } from "../../utils/types.ts";

export interface Props {
email: string;
password: string;
}

/**
* @title VTEX Integration - Authenticate with Email and Password
* @description This function authenticates a user using their email and password.
*/
export default async function action(
props: Props,
_req: Request,
ctx: AppContext,
): Promise<AuthResponse | null> {
const { vcsDeprecated } = ctx;

if (!props.email || !props.password) {
console.error("Email and/or password is missing:", props);
return null;
}

try {
const startAuthentication = await ctx.invoke.vtex.actions.authentication
.startAuthentication({});

if (!startAuthentication?.authenticationToken) {
console.error(
"No authentication token returned from startAuthentication.",
);
return null;
}

const authenticationToken = startAuthentication.authenticationToken;

const urlencoded = new URLSearchParams();
urlencoded.append("email", props.email);
urlencoded.append("password", props.password);
urlencoded.append("authenticationToken", authenticationToken);

const response = await vcsDeprecated
["POST /api/vtexid/pub/authentication/classic/validate"](
{},
{
body: urlencoded,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
},
);

if (!response.ok) {
console.error(
"Authentication request failed",
response.status,
response.statusText,
);
return null;
}

const data: AuthResponse = await response.json();
if (data.authStatus === "Success") {
const VTEXID_EXPIRES = data.expiresIn;

if (data.authCookie) {
setCookie(ctx.response.headers, {
name: data.authCookie.Name,
value: data.authCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}

if (data.accountAuthCookie) {
setCookie(ctx.response.headers, {
name: data.accountAuthCookie.Name,
value: data.accountAuthCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}
}

await ctx.invoke.vtex.actions.session.editSession({});

return data;
} catch (error) {
console.error("Unexpected error during authentication", error);
if (error instanceof Error) {
return safeParse<AuthResponse>(error.message);
}
return null;
}
}
33 changes: 33 additions & 0 deletions vtex/actions/authentication/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getCookies, setCookie } from "std/http/mod.ts";
import { AppContext } from "../../mod.ts";
import { parseCookie } from "../../utils/vtexId.ts";

export default async function logout(
_: unknown,
req: Request,
ctx: AppContext,
) {
const cookies = getCookies(req.headers);
const { payload } = parseCookie(req.headers, ctx.account);

for (const cookieName in cookies) {
if (cookieName.startsWith("VtexIdclientAutCookie")) {
setCookie(ctx.response.headers, {
name: cookieName,
value: "",
expires: new Date(0),
path: "/",
});
}
}

try {
if (payload?.sess) {
await ctx.invoke.vtex.actions.session.deleteSession({
sessionId: payload?.sess,
});
}
} catch (error) {
console.error("Error deleting session", error);
}
}
107 changes: 107 additions & 0 deletions vtex/actions/authentication/recoveryPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { getCookies } from "std/http/cookie.ts";
import { setCookie } from "std/http/mod.ts";
import safeParse from "../../../utils/safeParse.ts";
import { AppContext } from "../../mod.ts";
import { getSegmentFromBag } from "../../utils/segment.ts";
import { AuthResponse } from "../../utils/types.ts";

export interface Props {
email: string;
newPassword: string;
accessKey: string;
}

/**
* @title VTEX Integration - Recovery password
* @description
*/
export default async function action(
props: Props,
req: Request,
ctx: AppContext,
): Promise<AuthResponse | null> {
const { vcsDeprecated, account } = ctx;
const segment = getSegmentFromBag(ctx);

if (!props.email || !props.accessKey || !props.newPassword) {
console.error("Email, accessKey and/or newPassword is missing:", props);
return null;
}

try {
const cookies = getCookies(req.headers);
const VtexSessionToken = cookies?.["VtexSessionToken"] ?? null;

if (!VtexSessionToken) {
console.error("VtexSessionToken is missing");
return null;
}

const urlencoded = new URLSearchParams();
urlencoded.append("login", props.email);
urlencoded.append("accessKey", props.accessKey);
urlencoded.append("newPassword", props.newPassword);
urlencoded.append("authenticationToken", VtexSessionToken);

const response = await vcsDeprecated
["POST /api/vtexid/pub/authentication/classic/setpassword"](
{
locale: segment?.payload.cultureInfo || "pt-BR",
scope: account,
},
{
body: urlencoded,
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
},
);

if (!response.ok) {
console.error(
"Authentication request failed",
response.status,
response.statusText,
);
return null;
}

const data: AuthResponse = await response.json();
if (data.authStatus === "Success") {
const VTEXID_EXPIRES = data.expiresIn;

if (data.authCookie) {
setCookie(ctx.response.headers, {
name: data.authCookie.Name,
value: data.authCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}

if (data.accountAuthCookie) {
setCookie(ctx.response.headers, {
name: data.accountAuthCookie.Name,
value: data.accountAuthCookie.Value,
httpOnly: true,
maxAge: VTEXID_EXPIRES,
path: "/",
secure: true,
});
}
}

await ctx.invoke.vtex.actions.session.editSession({});

return data;
} catch (error) {
console.error("Unexpected error during authentication", error);
if (error instanceof Error) {
return safeParse<AuthResponse>(error.message);
}
return null;
}
}
Loading

0 comments on commit 1bb14c1

Please sign in to comment.