Skip to content

Commit

Permalink
feat: add save token, add auth and anonym client
Browse files Browse the repository at this point in the history
  • Loading branch information
YulikK committed May 9, 2024
1 parent 37be0e8 commit ab31b8a
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 119 deletions.
7 changes: 3 additions & 4 deletions .env
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

VITE_APP_CTP_PROJECT_KEY=green-shop
VITE_APP_CTP_CLIENT_SECRET=wzD7hatuZ8giFcGL5h6htyGtNlAl1chR
VITE_APP_CTP_CLIENT_ID=LG1VRj2-lAjAdT3Wa0m9AXou
VITE_APP_CTP_CLIENT_SECRET=Nlpz1PodM9gnmEPEwHw91jhXv77qU4ww
VITE_APP_CTP_CLIENT_ID=BPJMQ0wyC-4dA_1sd04SlJQx
VITE_APP_CTP_REGION=europe-west1
VITE_APP_CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com
VITE_APP_CTP_API_URL=https://api.europe-west1.gcp.commercetools.com
VITE_APP_CTP_SCOPES=manage_orders:green-shop manage_payments:green-shop manage_types:green-shop view_shopping_lists:green-shop manage_customers:green-shop manage_my_orders:green-shop view_orders:green-shop manage_quotes:green-shop view_discount_codes:green-shop view_quote_requests:green-shop manage_order_edits:green-shop manage_products:green-shop manage_quote_requests:green-shop view_quotes:green-shop view_api_clients:green-shop view_order_edits:green-shop manage_shipping_methods:green-shop manage_cart_discounts:green-shop manage_my_shopping_lists:green-shop view_products:green-shop view_categories:green-shop manage_my_payments:green-shop manage_my_profile:green-shop manage_discount_codes:green-shop manage_categories:green-shop manage_shopping_lists:green-shop manage_extensions:green-shop
VITE_APP_CTP_SCOPES=manage_products:green-shop manage_sessions:green-shop manage_my_quotes:green-shop manage_customer_groups:green-shop manage_payments:green-shop manage_checkout_payment_intents:green-shop manage_api_clients:green-shop manage_order_edits:green-shop manage_project:green-shop manage_orders:green-shop create_anonymous_token:green-shop manage_business_units:green-shop manage_associate_roles:green-shop manage_product_selections:green-shop view_api_clients:green-shop view_audit_log:green-shop manage_cart_discounts:green-shop manage_my_shopping_lists:green-shop manage_connectors:green-shop manage_customers:green-shop introspect_oauth_tokens:green-shop manage_my_orders:green-shop manage_my_payments:green-shop manage_my_profile:green-shop manage_connectors_deployments:green-shop manage_audit_log:green-shop manage_attribute_groups:green-shop manage_my_quote_requests:green-shop manage_discount_codes:green-shop manage_categories:green-shop manage_extensions:green-shop manage_my_business_units:green-shop manage_import_containers:green-shop
VITE_APP_DEFAULT_SEGMENT='/'
VITE_APP_NEXT_SEGMENT=1
VITE_APP_PATH_SEGMENTS_TO_KEEP=0
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@
"@commercetools/sdk-client-v2": "^2.5.0",
"@commercetools/sdk-middleware-auth": "^7.0.1",
"@commercetools/sdk-middleware-http": "^7.0.4",
"@types/js-cookie": "^3.0.6",
"autoprefixer": "^10.4.19",
"isomorphic-fetch": "^3.0.0",
"js-cookie": "^3.0.5",
"materialize-css": "^1.0.0-rc.2",
"modern-normalize": "^2.0.0",
"postcode-validator": "^3.8.20",
Expand Down
41 changes: 20 additions & 21 deletions src/shared/API/customer/model/CustomerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
Customer,
CustomerPagedQueryResponse,
CustomerSignInResult,
CustomerUpdateAction,
MyCustomerUpdateAction,
} from '@commercetools/platform-sdk';

import getRoot, { type RootApi } from '../../sdk/root.ts';
Expand All @@ -24,51 +24,51 @@ export class CustomerModel {
this.root = getRoot();
}

public static actionAddAddress(address: Address): CustomerUpdateAction {
public static actionAddAddress(address: Address): MyCustomerUpdateAction {
return { action: 'addAddress', address: CustomerModel.adaptAddressToServer(address) };
}

public static actionEditAddress(address: Address): CustomerUpdateAction {
public static actionEditAddress(address: Address): MyCustomerUpdateAction {
return { action: 'changeAddress', address: CustomerModel.adaptAddressToServer(address), addressId: address.id };
}

public static actionEditDateOfBirth(dateOfBirth: string): CustomerUpdateAction {
public static actionEditDateOfBirth(dateOfBirth: string): MyCustomerUpdateAction {
return { action: 'setDateOfBirth', dateOfBirth };
}

public static actionEditDefaultBillingAddress(addressId: string): CustomerUpdateAction {
public static actionEditDefaultBillingAddress(addressId: string): MyCustomerUpdateAction {
return { action: 'setDefaultBillingAddress', addressId };
}

public static actionEditDefaultShippingAddress(addressId: string): CustomerUpdateAction {
public static actionEditDefaultShippingAddress(addressId: string): MyCustomerUpdateAction {
return { action: 'setDefaultShippingAddress', addressId };
}

public static actionEditEmail(email: string): CustomerUpdateAction {
public static actionEditEmail(email: string): MyCustomerUpdateAction {
return { action: 'changeEmail', email };
}

public static actionEditFirstName(firstName: string): CustomerUpdateAction {
public static actionEditFirstName(firstName: string): MyCustomerUpdateAction {
return { action: 'setFirstName', firstName };
}

public static actionEditLastName(lastName: string): CustomerUpdateAction {
public static actionEditLastName(lastName: string): MyCustomerUpdateAction {
return { action: 'setLastName', lastName };
}

public static actionRemoveAddress(address: Address): CustomerUpdateAction {
public static actionRemoveAddress(address: Address): MyCustomerUpdateAction {
return { action: 'removeAddress', addressId: address.id };
}

public static actionRemoveBillingAddress(address: Address): CustomerUpdateAction {
public static actionRemoveBillingAddress(address: Address): MyCustomerUpdateAction {
return { action: 'removeBillingAddressId', addressId: address.id };
}

public static actionRemoveShippingAddress(address: Address): CustomerUpdateAction {
public static actionRemoveShippingAddress(address: Address): MyCustomerUpdateAction {
return { action: 'removeShippingAddressId', addressId: address.id };
}

public static actionSetLocale(locale: string): CustomerUpdateAction {
public static actionSetLocale(locale: string): MyCustomerUpdateAction {
return { action: 'setLocale', locale };
}

Expand Down Expand Up @@ -181,18 +181,13 @@ export class CustomerModel {
return this.getCustomerFromData(data) !== null;
}

public async editCustomer(actions: CustomerUpdateAction[], customer: User): Promise<User | null> {
const data = await this.root.editCustomer(actions, customer.version, customer.id);
public async editCustomer(actions: MyCustomerUpdateAction[], customer: User): Promise<User | null> {
const data = await this.root.editCustomer(actions, customer.version);
return this.getCustomerFromData(data);
}

public async editPassword(customer: User, currentPassword: string, newPassword: string): Promise<User | null> {
const data = await this.root.editPassword(customer.id, customer.version, currentPassword, newPassword);
return this.getCustomerFromData(data);
}

public async getCustomerByID(id: string): Promise<User | null> {
const data = await this.root.getCustomerByID(id);
const data = await this.root.editPassword(customer.version, currentPassword, newPassword);
return this.getCustomerFromData(data);
}

Expand All @@ -201,6 +196,10 @@ export class CustomerModel {
return this.getCustomerFromData(data);
}

public logout(): boolean {
return this.root.logoutUser();
}

public async registerNewCustomer(userData: User): Promise<User | null> {
const data = await this.root.registrationUser(userData);
return this.getCustomerFromData(data);
Expand Down
52 changes: 18 additions & 34 deletions src/shared/API/customer/tests/Customer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,22 @@ describe('Checking Customer Model', () => {
expect(customer).toHaveProperty('version');
});

it('should authenticate the customer', async () => {
auth = await customerModel.authCustomer(user);
expect(typeof auth).toBe('object');
expect(auth).toHaveProperty('addresses');
expect(auth).toHaveProperty('defaultBillingAddressId');
expect(auth).toHaveProperty('defaultShippingAddressId');
expect(auth).toHaveProperty('email');
expect(auth).toHaveProperty('firstName');
expect(auth).toHaveProperty('id');
expect(auth).toHaveProperty('lastName');
expect(auth).toHaveProperty('password');
expect(auth).toHaveProperty('version');
});

it('should edit the customer', async () => {
if (customer) {
if (auth) {
editCustomer = await customerModel.editCustomer(
[
CustomerModel.actionEditFirstName('John'),
Expand All @@ -84,7 +98,7 @@ describe('Checking Customer Model', () => {
CustomerModel.actionAddAddress(address),
CustomerModel.actionEditDateOfBirth('1990-01-01'),
],
customer,
auth,
);
expect(typeof editCustomer).toBe('object');
expect(editCustomer).toHaveProperty('addresses');
Expand Down Expand Up @@ -128,39 +142,9 @@ describe('Checking Customer Model', () => {
}
});

it('should found the customer by id', async () => {
if (customer) {
const getCustomer = await customerModel.getCustomerByID(customer.id);
expect(typeof getCustomer).toBe('object');
expect(getCustomer).toHaveProperty('addresses');
expect(getCustomer).toHaveProperty('defaultBillingAddressId');
expect(getCustomer).toHaveProperty('defaultShippingAddressId');
expect(getCustomer).toHaveProperty('email');
expect(getCustomer).toHaveProperty('firstName');
expect(getCustomer).toHaveProperty('id');
expect(getCustomer).toHaveProperty('lastName');
expect(getCustomer).toHaveProperty('password');
expect(getCustomer).toHaveProperty('version');
}
});

it('should authenticate the customer', async () => {
auth = await customerModel.authCustomer({ email: '[email protected]', password: 'Qqq11' });
expect(typeof auth).toBe('object');
expect(auth).toHaveProperty('addresses');
expect(auth).toHaveProperty('defaultBillingAddressId');
expect(auth).toHaveProperty('defaultShippingAddressId');
expect(auth).toHaveProperty('email');
expect(auth).toHaveProperty('firstName');
expect(auth).toHaveProperty('id');
expect(auth).toHaveProperty('lastName');
expect(auth).toHaveProperty('password');
expect(auth).toHaveProperty('version');
});

it('should edit the customer password', async () => {
if (auth) {
editPassword = await customerModel.editPassword(auth, 'Qqq11', 'Qqq11');
if (editAddress) {
editPassword = await customerModel.editPassword(editAddress, 'Qqq11', 'Qqq11');
expect(typeof editPassword).toBe('object');
expect(editPassword).toHaveProperty('addresses');
expect(editPassword).toHaveProperty('defaultBillingAddressId');
Expand Down
169 changes: 151 additions & 18 deletions src/shared/API/sdk/client.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,167 @@
import type { UserCredentials } from '@/shared/types/user';

import { type ByProjectKeyRequestBuilder, createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
import {
type AuthMiddlewareOptions,
type Client,
ClientBuilder,
type HttpMiddlewareOptions,
type Middleware,
type PasswordAuthMiddlewareOptions,
type RefreshAuthMiddlewareOptions,
createAuthForAnonymousSessionFlow,
createAuthForClientCredentialsFlow,
createAuthForPasswordFlow,
} from '@commercetools/sdk-client-v2';

import { TokenType } from '../types/type.ts';
import getTokenCache from './token-cache/token-cache.ts';

const URL_AUTH = 'https://auth.europe-west1.gcp.commercetools.com';
const URL_HTTP = 'https://api.europe-west1.gcp.commercetools.com';
const USE_SAVE_TOKEN = false;

const httpMiddlewareOptions: HttpMiddlewareOptions = {
fetch,
host: URL_HTTP,
};

export default function client(projectKey: string, clientID: string, clientSecret: string, scopes: string): Client {
const scopesArr = scopes.split(',');
const authMiddlewareOptions: AuthMiddlewareOptions = {
credentials: {
clientId: clientID,
clientSecret,
},
fetch,
host: URL_AUTH,
projectKey,
scopes: scopesArr,
};

return new ClientBuilder()
.withProjectKey(projectKey)
.withHttpMiddleware(httpMiddlewareOptions)
.withClientCredentialsFlow(authMiddlewareOptions)
.build();
export default class ApiClient {
private adminConnection: ByProjectKeyRequestBuilder;

private anonymConnection: ByProjectKeyRequestBuilder | null = null;

private authConnection: ByProjectKeyRequestBuilder | null = null;

private clientID: string;

private clientSecret: string;

private isAuth = false;

private projectKey: string;

private scopes: string[];

constructor(projectKey: string, clientID: string, clientSecret: string, scopes: string) {
this.projectKey = projectKey;
this.clientID = clientID;
this.clientSecret = clientSecret;
this.scopes = scopes.split(',');

this.anonymConnection = this.createAnonymConnection();

const adminOptions = createAuthForClientCredentialsFlow({
credentials: {
clientId: this.clientID,
clientSecret: this.clientSecret,
},
fetch,
host: URL_AUTH,
projectKey: this.projectKey,
scopes: this.scopes,
});

const adminClient = this.getAdminClient(adminOptions);

this.adminConnection = this.getConnection(adminClient);
}

private getAdminClient(middleware: Middleware): Client {
return new ClientBuilder()
.withProjectKey(this.projectKey)
.withHttpMiddleware(httpMiddlewareOptions)
.withMiddleware(middleware)
.build();
}

private getAuthOption(credentials: UserCredentials): Middleware {
const { email, password } = credentials || { email: '', password: '' };
const defaultOptions = this.getDefaultOptions(TokenType.AUTH);
const authOptions: PasswordAuthMiddlewareOptions = {
...defaultOptions,
credentials: {
...defaultOptions.credentials,
user: {
password,
username: email,
},
},
};
return createAuthForPasswordFlow(authOptions);
}

private getClient(middleware: Middleware, token: TokenType): Client {
const defaultOptions = this.getDefaultOptions(token);
const opt: RefreshAuthMiddlewareOptions = {
...defaultOptions,
refreshToken: token,
};
return new ClientBuilder()
.withProjectKey(this.projectKey)
.withHttpMiddleware(httpMiddlewareOptions)
.withMiddleware(middleware)
.withRefreshTokenFlow(opt)
.build();
}

private getConnection(client: Client): ByProjectKeyRequestBuilder {
return createApiBuilderFromCtpClient(client).withProjectKey({ projectKey: this.projectKey });
}

private getDefaultOptions(tokenType: TokenType): AuthMiddlewareOptions {
return {
credentials: {
clientId: this.clientID,
clientSecret: this.clientSecret,
},
fetch,
host: URL_AUTH,
projectKey: this.projectKey,
scopes: this.scopes,
tokenCache: USE_SAVE_TOKEN ? getTokenCache(tokenType) : undefined,
};
}

public adminRoot(): ByProjectKeyRequestBuilder {
return this.adminConnection;
}

public apiRoot(): ByProjectKeyRequestBuilder {
let client = this.authConnection && this.isAuth ? this.authConnection : this.anonymConnection;
if (!client) {
client = this.createAnonymConnection();
}
return client;
}

public approveAuth(): boolean {
this.isAuth = true;
return this.isAuth;
}

public createAnonymConnection(): ByProjectKeyRequestBuilder {
const defaultOptions = this.getDefaultOptions(TokenType.ANONYM);
const anonymOptions = createAuthForAnonymousSessionFlow(defaultOptions);
const anonymClient = this.getClient(anonymOptions, TokenType.ANONYM);
this.anonymConnection = this.getConnection(anonymClient);

return this.anonymConnection;
}

public createAuthConnection(credentials: UserCredentials): ByProjectKeyRequestBuilder {
if (!this.authConnection || (this.authConnection && !this.isAuth)) {
const authOptions = this.getAuthOption(credentials);
const authClient = this.getClient(authOptions, TokenType.AUTH);
this.authConnection = this.getConnection(authClient);
}
return this.authConnection;
}

public deleteAuthConnection(): boolean {
this.authConnection = null;
this.isAuth = false;
this.anonymConnection = this.createAnonymConnection();
return this.authConnection === null;
}
}
Loading

0 comments on commit ab31b8a

Please sign in to comment.