Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(VTEX): Implement Authentication APIs #1024

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
Loading