@geins/crm 0.1.1-canary

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/__tests__/GeinsCRM.auth.test.ts +284 -0
  4. package/__tests__/GeinsCRM.user.test.ts +109 -0
  5. package/dist/auth/authClient.d.ts +41 -0
  6. package/dist/auth/authClientDirect.d.ts +11 -0
  7. package/dist/auth/authClientProxy.d.ts +12 -0
  8. package/dist/auth/authHelpers.d.ts +5 -0
  9. package/dist/auth/authService.d.ts +17 -0
  10. package/dist/auth/authServiceClient.d.ts +20 -0
  11. package/dist/auth/index.d.ts +6 -0
  12. package/dist/geinsCRM.d.ts +34 -0
  13. package/dist/graphql/index.d.ts +1 -0
  14. package/dist/graphql/queries.d.ts +12 -0
  15. package/dist/index.cjs +24358 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.esm.js +24346 -0
  18. package/dist/parsers/shared.d.ts +2 -0
  19. package/dist/services/index.d.ts +3 -0
  20. package/dist/services/pwResetService.d.ts +6 -0
  21. package/dist/services/userOrdersService.d.ts +9 -0
  22. package/dist/services/userService.d.ts +13 -0
  23. package/dist/types/crmTypes.d.ts +36 -0
  24. package/dist/types/index.d.ts +1 -0
  25. package/eslint.config.js +3 -0
  26. package/jest.config.js +8 -0
  27. package/package.json +30 -0
  28. package/rollup.config.js +26 -0
  29. package/src/auth/authClient.ts +318 -0
  30. package/src/auth/authClientDirect.ts +31 -0
  31. package/src/auth/authClientProxy.ts +82 -0
  32. package/src/auth/authHelpers.ts +65 -0
  33. package/src/auth/authService.ts +175 -0
  34. package/src/auth/authServiceClient.ts +267 -0
  35. package/src/auth/index.ts +6 -0
  36. package/src/geinsCRM.ts +306 -0
  37. package/src/graphql/auth/pw-reset-commit.gql +15 -0
  38. package/src/graphql/auth/pw-reset-request.gql +13 -0
  39. package/src/graphql/fragments/address.gql +16 -0
  40. package/src/graphql/fragments/balances.gql +5 -0
  41. package/src/graphql/fragments/campaign.gql +4 -0
  42. package/src/graphql/fragments/cart.gql +101 -0
  43. package/src/graphql/fragments/price.gql +13 -0
  44. package/src/graphql/fragments/stock.gql +7 -0
  45. package/src/graphql/fragments/user.gql +16 -0
  46. package/src/graphql/graphql.d.ts +9 -0
  47. package/src/graphql/index.ts +1 -0
  48. package/src/graphql/order/orders.gql +39 -0
  49. package/src/graphql/queries.ts +21 -0
  50. package/src/graphql/user/delete.gql +3 -0
  51. package/src/graphql/user/get.gql +6 -0
  52. package/src/graphql/user/register.gql +15 -0
  53. package/src/graphql/user/update.gql +16 -0
  54. package/src/index.ts +2 -0
  55. package/src/parsers/index.ts +0 -0
  56. package/src/parsers/shared.ts +23 -0
  57. package/src/services/index.ts +3 -0
  58. package/src/services/pwResetService.ts +30 -0
  59. package/src/services/userOrdersService.ts +34 -0
  60. package/src/services/userService.ts +83 -0
  61. package/src/types/crmTypes.ts +46 -0
  62. package/src/types/index.ts +1 -0
  63. package/tsconfig.json +19 -0
@@ -0,0 +1,175 @@
1
+ /*
2
+ SERVER OR/AND CLIENT
3
+ */
4
+ import { AUTH_COOKIES, CookieService, logWrite } from '@geins/core';
5
+ import type { AuthResponse, AuthCredentials, AuthTokens } from '@geins/types';
6
+ import { AuthServiceClient } from './authServiceClient';
7
+ import { authClaimsTokenSerializeToObject } from './authHelpers';
8
+
9
+ const EXPIRES_SOON_THRESHOLD = 90;
10
+
11
+ export class AuthService {
12
+ private signEndpoint: string;
13
+ private authEndpoint: string;
14
+ private client: AuthServiceClient | undefined;
15
+ private cookieService: CookieService;
16
+
17
+ constructor(signEndpoint: string, authEndpoint: string) {
18
+ this.signEndpoint = signEndpoint;
19
+ this.authEndpoint = authEndpoint;
20
+ this.cookieService = new CookieService();
21
+ this.initClient();
22
+ }
23
+ // initialize client
24
+ private initClient(): void {
25
+ this.client = new AuthServiceClient(this.authEndpoint, this.signEndpoint);
26
+ }
27
+ // helper to make sure client is initialized
28
+ private ensureClientInitialized(): void {
29
+ if (!this.client) {
30
+ this.initClient();
31
+ }
32
+ if (!this.client) {
33
+ throw new Error('AuthServiceClient is not initialized');
34
+ }
35
+ }
36
+
37
+ // login in user
38
+ public async login(credentials: AuthCredentials): Promise<AuthResponse> {
39
+ try {
40
+ this.ensureClientInitialized();
41
+
42
+ const result = await this.client!.login(
43
+ credentials.username,
44
+ credentials.password,
45
+ credentials.rememberUser,
46
+ );
47
+
48
+ return AuthService.getUserObjectFromToken(result.token, result.refreshToken);
49
+ } catch (error) {
50
+ return this.handleError('Login failed', error);
51
+ }
52
+ }
53
+
54
+ // get user if userToken is provided parse from token if not use refresh token to get user
55
+ public async getUser(refreshToken: string, userToken?: string): Promise<AuthResponse> {
56
+ try {
57
+ if (userToken) {
58
+ const authResponse = AuthService.getUserObjectFromToken(userToken, refreshToken);
59
+ if (!authResponse) {
60
+ return { succeeded: false };
61
+ }
62
+ if (authResponse.tokens?.expiresSoon) {
63
+ return await this.refresh(refreshToken);
64
+ }
65
+
66
+ return authResponse;
67
+ } else {
68
+ return await this.refresh(refreshToken);
69
+ }
70
+ } catch (error) {
71
+ return this.handleError('Get user failed', error);
72
+ }
73
+ }
74
+
75
+ // change password
76
+ public async changePassword(credentials: AuthCredentials, refreshToken: string): Promise<AuthResponse> {
77
+ if (!credentials.newPassword || !refreshToken) {
78
+ return { succeeded: false };
79
+ }
80
+ try {
81
+ this.ensureClientInitialized();
82
+ const result = await this.client!.changePassword(credentials, refreshToken);
83
+ if (!result.token) {
84
+ return { succeeded: false };
85
+ }
86
+ const authResponse = AuthService.getUserObjectFromToken(result.token, result.refreshToken);
87
+ if (!authResponse) {
88
+ return { succeeded: false };
89
+ }
90
+ return authResponse;
91
+ } catch (error) {
92
+ return this.handleError('Token refresh failed', error);
93
+ }
94
+ }
95
+
96
+ // get new refresh token and token
97
+ public async refresh(refreshToken: string): Promise<AuthResponse> {
98
+ if (!refreshToken) {
99
+ return { succeeded: false };
100
+ }
101
+
102
+ try {
103
+ this.ensureClientInitialized();
104
+ const result = await this.client!.renewRefreshtoken(refreshToken);
105
+ if (!result.token) {
106
+ return { succeeded: false };
107
+ }
108
+ const authResponse = AuthService.getUserObjectFromToken(result.token, result.refreshToken);
109
+ if (!authResponse) {
110
+ return { succeeded: false };
111
+ }
112
+ return authResponse;
113
+ } catch (error) {
114
+ return this.handleError('Token refresh failed', error);
115
+ }
116
+ }
117
+
118
+ // register new user
119
+ public async register(credentials: AuthCredentials): Promise<AuthResponse> {
120
+ try {
121
+ this.ensureClientInitialized();
122
+ const result = await this.client!.register(credentials.username, credentials.password);
123
+
124
+ if (!result) {
125
+ return { succeeded: false };
126
+ }
127
+
128
+ return AuthService.getUserObjectFromToken(result.token, result.refreshToken);
129
+ } catch (error) {
130
+ return this.handleError('Register new user failed', error);
131
+ }
132
+ }
133
+
134
+ // serialize user from jwt token
135
+ static getUserObjectFromToken(userToken: string, refreshToken?: string): AuthResponse {
136
+ try {
137
+ const userFromToken = authClaimsTokenSerializeToObject(userToken);
138
+ if (!userFromToken) {
139
+ throw new Error('Failed to parse user token');
140
+ }
141
+
142
+ const now = Math.floor(Date.now() / 1000);
143
+ const expiresIn = parseInt(userFromToken.exp || '0') - now;
144
+ const expiresSoon = expiresIn < EXPIRES_SOON_THRESHOLD;
145
+
146
+ return {
147
+ succeeded: true,
148
+ user: {
149
+ authenticated: !userFromToken.expired,
150
+ userId: userFromToken.sid || '',
151
+ username: userFromToken.name || 'unknown',
152
+ customerType: (userFromToken.customerType || 'unknown').toUpperCase(),
153
+ memberDiscount: userFromToken.memberDiscount || '0',
154
+ memberType: userFromToken.memberType || 'unknown',
155
+ memberId: userFromToken.memberId || '0',
156
+ },
157
+ tokens: {
158
+ token: userToken,
159
+ expires: parseInt(userFromToken.exp || '0'),
160
+ expired: now >= parseInt(userFromToken.exp || '0'),
161
+ expiresSoon,
162
+ expiresIn,
163
+ refreshToken: refreshToken,
164
+ },
165
+ };
166
+ } catch (error) {
167
+ throw new Error('Failed to parse user token');
168
+ }
169
+ }
170
+
171
+ // error handler
172
+ private handleError(message: string, error: unknown): any {
173
+ return { succeeded: false, error: { message, details: error } };
174
+ }
175
+ }
@@ -0,0 +1,267 @@
1
+ import {
2
+ logWrite,
3
+ AUTH_HEADERS,
4
+ type AuthCredentials,
5
+ type AuthUserToken,
6
+ type AuthSignature,
7
+ } from '@geins/core';
8
+ import { digest } from './authHelpers';
9
+
10
+ export class AuthServiceClient {
11
+ private authEndpoint: string;
12
+ private signEndpoint: string;
13
+
14
+ constructor(authEndpoint: string, signEndpoint: string) {
15
+ if (!authEndpoint || !signEndpoint) {
16
+ throw new Error('Both authEndpoint and signEndpoint are required');
17
+ }
18
+ this.authEndpoint = authEndpoint;
19
+ this.signEndpoint = signEndpoint;
20
+ }
21
+
22
+ private getAuthEndpointUrl(endpoint: string): string {
23
+ return `${this.authEndpoint}/${endpoint}`;
24
+ }
25
+
26
+ private getSignEndpointUrl(signature: string): string {
27
+ return `${this.signEndpoint}${encodeURIComponent(signature)}`;
28
+ }
29
+
30
+ private extractRefreshTokenFromResponse(response: Response): string {
31
+ const refreshTokenHeader = response.headers.get(AUTH_HEADERS.REFRESH_TOKEN);
32
+ if (!refreshTokenHeader) {
33
+ throw new Error('Error');
34
+ }
35
+ return refreshTokenHeader;
36
+ }
37
+
38
+ private async requestAuthChallenge(username: string): Promise<string> {
39
+ const url = this.getAuthEndpointUrl('login');
40
+ const options: RequestInit = {
41
+ method: 'POST',
42
+ cache: 'no-cache',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ body: JSON.stringify({ username }),
47
+ };
48
+ const response = await fetch(url, options);
49
+ const text = await response.text();
50
+ const retval = JSON.parse(text);
51
+ if (!retval?.sign) {
52
+ throw new Error('Failed to fetch sign');
53
+ }
54
+ return retval.sign;
55
+ }
56
+
57
+ private async verifyAuthChallenge(signatureToken: string): Promise<AuthSignature> {
58
+ const url = this.getSignEndpointUrl(signatureToken);
59
+ const response = await fetch(url, {
60
+ method: 'GET',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ });
65
+ if (!response.ok) {
66
+ throw new Error('Failed to verify challenge');
67
+ }
68
+ const text = await response.text();
69
+ if (!text) {
70
+ throw new Error('Failed to verify challenge: Empty response');
71
+ }
72
+ return JSON.parse(text);
73
+ }
74
+
75
+ private async fetchUserToken(
76
+ username: string,
77
+ password: string,
78
+ rememberUser: boolean,
79
+ ): Promise<AuthUserToken> {
80
+ const url = this.getAuthEndpointUrl('login');
81
+
82
+ const challangeToken = await this.requestAuthChallenge(username);
83
+ const authenticationSignature = await this.verifyAuthChallenge(challangeToken);
84
+ const requestBody: Record<string, any> = {
85
+ username,
86
+ signature: authenticationSignature,
87
+ password: await digest(password),
88
+ ...(rememberUser ? {} : { sessionLifetime: 30 }),
89
+ };
90
+
91
+ const requestOptions: RequestInit = {
92
+ method: 'POST',
93
+ cache: 'no-cache',
94
+ headers: {
95
+ 'Content-Type': 'application/json',
96
+ },
97
+ body: JSON.stringify(requestBody),
98
+ };
99
+
100
+ const response = await fetch(url, requestOptions);
101
+ if (!response.ok) {
102
+ throw new Error(`Failed to fetch user token: ${response.statusText}`);
103
+ }
104
+
105
+ const refreshToken = this.extractRefreshTokenFromResponse(response);
106
+
107
+ const userToken = await response.text();
108
+ if (!userToken) {
109
+ throw new Error('Failed to fetch user token: Empty response');
110
+ }
111
+
112
+ const retval = JSON.parse(userToken);
113
+ return {
114
+ maxAge: retval.maxAge,
115
+ token: retval.token,
116
+ refreshToken,
117
+ };
118
+ }
119
+
120
+ private async fetchRefreshToken(refreshToken: string): Promise<AuthUserToken> {
121
+ const url = this.getAuthEndpointUrl('login');
122
+ const requestOptions: RequestInit = {
123
+ method: 'GET',
124
+ cache: 'no-cache',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ [`${AUTH_HEADERS.REFRESH_TOKEN}`]: refreshToken,
128
+ },
129
+ };
130
+ const response = await fetch(url, requestOptions);
131
+ if (!response.ok) {
132
+ throw new Error(`Failed to renew refresh token: ${response.statusText}`);
133
+ }
134
+
135
+ const newRefreshToken = this.extractRefreshTokenFromResponse(response);
136
+
137
+ const userToken = await response.text();
138
+ if (!userToken) {
139
+ throw new Error('Failed to fetch user token: Empty response');
140
+ }
141
+
142
+ const retval = JSON.parse(userToken);
143
+ return {
144
+ maxAge: retval.maxAge,
145
+ token: retval.token,
146
+ refreshToken: newRefreshToken,
147
+ };
148
+ }
149
+
150
+ private async performChangePassword(
151
+ username: string,
152
+ currentPassword: string,
153
+ newPassword: string,
154
+ refreshToken: string,
155
+ ): Promise<AuthUserToken> {
156
+ const url = this.getAuthEndpointUrl('password');
157
+
158
+ const challangeToken = await this.requestAuthChallenge(username);
159
+ const authenticationSignature = await this.verifyAuthChallenge(challangeToken);
160
+
161
+ const requestBody: Record<string, any> = {
162
+ username,
163
+ signature: authenticationSignature,
164
+ password: await digest(currentPassword),
165
+ newPassword: await digest(newPassword),
166
+ };
167
+
168
+ const requestOptions: RequestInit = {
169
+ method: 'POST',
170
+ cache: 'no-cache',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ [`${AUTH_HEADERS.REFRESH_TOKEN}`]: refreshToken,
174
+ },
175
+ body: JSON.stringify(requestBody),
176
+ };
177
+
178
+ const response = await fetch(url, requestOptions);
179
+ if (!response.ok) {
180
+ throw new Error(`Failed to change password: ${response.statusText}`);
181
+ }
182
+
183
+ refreshToken = this.extractRefreshTokenFromResponse(response);
184
+
185
+ const userToken = await response.text();
186
+ if (!userToken) {
187
+ throw new Error('Failed to change password: Empty response');
188
+ }
189
+
190
+ const retval = JSON.parse(userToken);
191
+ return {
192
+ maxAge: retval.maxAge,
193
+ token: retval.token,
194
+ refreshToken,
195
+ };
196
+ }
197
+
198
+ private async performUserRegister(username: string, password: string): Promise<AuthUserToken> {
199
+ const url = this.getAuthEndpointUrl('register');
200
+
201
+ const challangeToken = await this.requestAuthChallenge(username);
202
+ const authenticationSignature = await this.verifyAuthChallenge(challangeToken);
203
+
204
+ const requestBody: Record<string, any> = {
205
+ username,
206
+ signature: authenticationSignature,
207
+ password: await digest(password),
208
+ };
209
+
210
+ const requestOptions: RequestInit = {
211
+ method: 'POST',
212
+ cache: 'no-cache',
213
+ headers: {
214
+ 'Content-Type': 'application/json',
215
+ },
216
+ body: JSON.stringify(requestBody),
217
+ };
218
+
219
+ const response = await fetch(url, requestOptions);
220
+ if (!response.ok) {
221
+ throw new Error(`Failed to register user: ${response.statusText}`);
222
+ }
223
+
224
+ const refreshToken = this.extractRefreshTokenFromResponse(response);
225
+
226
+ const userToken = await response.text();
227
+ if (!userToken) {
228
+ throw new Error('Failed to register user: Empty response');
229
+ }
230
+
231
+ const retval = JSON.parse(userToken);
232
+ return {
233
+ maxAge: retval.maxAge,
234
+ token: retval.token,
235
+ refreshToken,
236
+ };
237
+ }
238
+
239
+ public async register(username: string, password: string): Promise<AuthUserToken> {
240
+ return this.performUserRegister(username, password);
241
+ }
242
+
243
+ public async login(username: string, password: string, rememberUser?: boolean): Promise<AuthUserToken> {
244
+ return this.fetchUserToken(username, password, rememberUser!);
245
+ }
246
+
247
+ public async renewRefreshtoken(refreshToken: string): Promise<AuthUserToken> {
248
+ return this.fetchRefreshToken(refreshToken);
249
+ }
250
+
251
+ public async changePassword(credentials: AuthCredentials, refreshToken: string): Promise<AuthUserToken> {
252
+ if (!credentials.newPassword) {
253
+ throw new Error('New password is required');
254
+ }
255
+ return this.performChangePassword(
256
+ credentials.username,
257
+ credentials.password,
258
+ credentials.newPassword,
259
+ refreshToken,
260
+ );
261
+ }
262
+
263
+ public async logout(refreshToken: string): Promise<boolean> {
264
+ // Implementation if needed, currently just returning true
265
+ return true;
266
+ }
267
+ }
@@ -0,0 +1,6 @@
1
+ export * from './authHelpers';
2
+ export * from './authClient';
3
+ export * from './authClientDirect';
4
+ export * from './authClientProxy';
5
+ export * from './authService';
6
+ export * from './authServiceClient';