oro-sdk-apis 1.14.0 → 1.15.0

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.
@@ -2,9 +2,17 @@ import type { AxiosRequestConfig } from 'axios';
2
2
  import { AuthRefreshFunc, Tokens } from '../models';
3
3
  import { AxiosService } from './axios';
4
4
  export declare class APIService extends AxiosService {
5
+ private useLocalStorage;
5
6
  private tokenRefreshFailureCallback?;
6
7
  private authRefreshFn?;
7
- constructor(config?: AxiosRequestConfig, tokenRefreshFailureCallback?: ((err: Error) => void) | undefined);
8
+ private tokens;
9
+ /**
10
+ * The API Service lets you use an axios API and handles oro backend services authentification via JWT tokens
11
+ * @param useLocalStorage if set to true, tokens will be stored in localStorage
12
+ * @param config (optional) an axios config
13
+ * @param tokenRefreshFailureCallback (optional) callback to call when failing to refresh the auth token
14
+ */
15
+ constructor(useLocalStorage: boolean, config?: AxiosRequestConfig, tokenRefreshFailureCallback?: ((err: Error) => void) | undefined);
8
16
  setAuthRefreshFn(fn: AuthRefreshFunc): void;
9
17
  setTokens(tokens: Tokens): void;
10
18
  getTokens(): Tokens;
@@ -0,0 +1,25 @@
1
+ import { AuthTokenResponse, ServiceCollection, ServiceCollectionRequest } from '../models';
2
+ import { GuardService } from './guard';
3
+ /**
4
+ * This service enables you to handle one authentication token per practice
5
+ */
6
+ export declare class ApisPracticeManager {
7
+ private serviceCollReq;
8
+ private getAuthTokenCbk;
9
+ private useLocalStorage;
10
+ private practiceInstances;
11
+ /**
12
+ * The constructor
13
+ * @param serviceCollReq the services to initialize. Only filled urls will get corresponding service to be initialized.
14
+ * It will be used each time a new practices needs a `ServiceCollection`
15
+ * @param getAuthTokenCbk the callback function used to get a new JWT token
16
+ * @param useLocalStorage (default: false) if true store tokens into local storage (only for browsers)
17
+ */
18
+ constructor(serviceCollReq: ServiceCollectionRequest, getAuthTokenCbk: (guard: GuardService, practiceUuid: string) => Promise<AuthTokenResponse>, useLocalStorage?: boolean);
19
+ /**
20
+ * This function is used to get a `ServiceCollection` associated to a practice. If missing, it will initialize a new `ServiceCollection`.
21
+ * @param practiceUuid the uuid of the practice
22
+ * @returns a promise holding a `ServiceCollection`
23
+ */
24
+ get(practiceUuid: string): Promise<ServiceCollection>;
25
+ }
@@ -1,5 +1,5 @@
1
1
  import type { AxiosAuthRefreshRequestConfig } from 'axios-auth-refresh';
2
- import { AuthRecoverRequest, AuthTokenRequest, AuthTokenResponse, Base64String, IdentityCreateRequest, IdentityResendConfirmEmailRequest, IdentityResponse, IdentityUpdateRequest, QRCodeResponse, Tokens, Uuid, WhoAmIResponse } from '../models';
2
+ import { AuthRecoverRequest, AuthTokenRequest, AuthTokenResponse, Base64String, IdentityCreateRequest, IdentityResendConfirmEmailRequest, IdentityResponse, IdentityUpdateRequest, M2MTokenRequest, QRCodeResponse, Tokens, Uuid, WhoAmIResponse } from '../models';
3
3
  import { APIService } from './api';
4
4
  export interface GuardRequestConfig extends AxiosAuthRefreshRequestConfig {
5
5
  useRefreshToken: boolean;
@@ -22,6 +22,13 @@ export declare class GuardService {
22
22
  * @param tokens
23
23
  */
24
24
  setTokens(tokens: Tokens): void;
25
+ /**
26
+ * Allow to retrieve a M2M token for a service
27
+ *
28
+ * @param req The credentials required to get an access token
29
+ * @returns AuthTokenResponse
30
+ */
31
+ m2mToken(req: M2MTokenRequest): Promise<AuthTokenResponse>;
25
32
  /**
26
33
  * Allow to retrieve an access token and a refresh token in order
27
34
  * to do authenticated request afterward
@@ -1,4 +1,5 @@
1
1
  export * from './api';
2
+ export * from './apisPracticeManager';
2
3
  export * from './axios';
3
4
  export * from './consult';
4
5
  export * from './diagnosis';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.14.0",
2
+ "version": "1.15.0",
3
3
  "main": "dist/index.js",
4
4
  "typings": "dist/index.d.ts",
5
5
  "files": [
@@ -0,0 +1,11 @@
1
+ import { sha256 } from 'hash.js'
2
+ import { Buffer } from 'buffer/'
3
+
4
+ /**
5
+ * This function return a base64 string representation of a hashed string
6
+ * @param value the string to hash
7
+ * @returns a base64 string representation of a hashed value
8
+ */
9
+ export function hashToBase64String(value: string): string {
10
+ return Buffer.from(sha256().update(value).digest('hex'), 'hex').toString('base64')
11
+ }
@@ -0,0 +1,2 @@
1
+ export * from './hash'
2
+ export * from './init'
@@ -0,0 +1,47 @@
1
+ import { ServiceCollection, ServiceCollectionRequest } from '../models'
2
+ import {
3
+ APIService,
4
+ ConsultService,
5
+ DiagnosisService,
6
+ GuardService,
7
+ PracticeService,
8
+ TellerService,
9
+ VaultService,
10
+ WorkflowService,
11
+ } from '../services'
12
+
13
+ /**
14
+ * This function is used to initialize services with a provided url
15
+ * @param services an object containing the url of the services to init
16
+ * @param authenticationCallback (optional) the authentification callback. Called when the token were not able to be refreshed.
17
+ * @param useLocalStorage (default: true) if true store tokens into local storage (only for browsers)
18
+ * @returns an instance of each services with a provided url
19
+ */
20
+ export const init = (
21
+ services: ServiceCollectionRequest,
22
+ authenticationCallback?: (err: Error, practiceUuid?: string) => void,
23
+ useLocalStorage = true
24
+ ): ServiceCollection => {
25
+ const {
26
+ tellerBaseURL,
27
+ practiceBaseURL,
28
+ consultBaseURL,
29
+ vaultBaseURL,
30
+ guardBaseURL,
31
+ workflowBaseURL,
32
+ diagnosisBaseURL,
33
+ } = services
34
+
35
+ const apiService = new APIService(useLocalStorage, undefined, authenticationCallback)
36
+
37
+ return {
38
+ apiService,
39
+ tellerService: tellerBaseURL ? new TellerService(apiService, tellerBaseURL) : undefined,
40
+ practiceService: practiceBaseURL ? new PracticeService(apiService, practiceBaseURL) : undefined,
41
+ consultService: consultBaseURL ? new ConsultService(apiService, consultBaseURL) : undefined,
42
+ vaultService: vaultBaseURL ? new VaultService(apiService, vaultBaseURL) : undefined,
43
+ guardService: guardBaseURL ? new GuardService(apiService, guardBaseURL) : undefined,
44
+ workflowService: workflowBaseURL ? new WorkflowService(apiService, workflowBaseURL) : undefined,
45
+ diagnosisService: diagnosisBaseURL ? new DiagnosisService(apiService, diagnosisBaseURL) : undefined,
46
+ }
47
+ }
package/src/index.ts CHANGED
@@ -1,56 +1,6 @@
1
- import {
2
- APIService,
3
- TellerService,
4
- VaultService,
5
- GuardService,
6
- PracticeService,
7
- ConsultService,
8
- WorkflowService,
9
- DiagnosisService,
10
- } from './services'
11
-
12
- /**
13
- * This function is used to initialize services with a provided url
14
- * @param services an object containing the url of the services to init
15
- * @param (optional) authenticationCallback the authentification callback
16
- * @returns an instance of each services with a provided url
17
- */
18
- const init = (
19
- services: {
20
- tellerBaseURL?: string
21
- vaultBaseURL?: string
22
- guardBaseURL?: string
23
- practiceBaseURL?: string
24
- consultBaseURL?: string
25
- workflowBaseURL?: string
26
- diagnosisBaseURL?: string
27
- },
28
- authenticationCallback?: (err: Error) => void
29
- ) => {
30
- const {
31
- tellerBaseURL,
32
- practiceBaseURL,
33
- consultBaseURL,
34
- vaultBaseURL,
35
- guardBaseURL,
36
- workflowBaseURL,
37
- diagnosisBaseURL,
38
- } = services
39
-
40
- const apiService = new APIService(undefined, authenticationCallback)
41
-
42
- return {
43
- tellerService: tellerBaseURL ? new TellerService(apiService, tellerBaseURL) : undefined,
44
- practiceService: practiceBaseURL ? new PracticeService(apiService, practiceBaseURL) : undefined,
45
- consultService: consultBaseURL ? new ConsultService(apiService, consultBaseURL) : undefined,
46
- vaultService: vaultBaseURL ? new VaultService(apiService, vaultBaseURL) : undefined,
47
- guardService: guardBaseURL ? new GuardService(apiService, guardBaseURL) : undefined,
48
- workflowService: workflowBaseURL ? new WorkflowService(apiService, workflowBaseURL) : undefined,
49
- diagnosisService: diagnosisBaseURL ? new DiagnosisService(apiService, diagnosisBaseURL) : undefined,
50
- }
51
- }
52
-
1
+ import { init } from './helpers'
53
2
 
3
+ export * from './helpers'
54
4
  export * from './models'
55
5
  export * from './services'
56
6
  export default init
@@ -1,8 +1,6 @@
1
1
  import { Uuid, Base64String, TokenData, RFC3339Date, Url } from './'
2
2
 
3
- export type AuthRefreshFunc = (
4
- refreshToken?: string
5
- ) => Promise<AuthTokenResponse>
3
+ export type AuthRefreshFunc = (refreshToken?: string) => Promise<AuthTokenResponse>
6
4
  export interface Tokens {
7
5
  accessToken?: string
8
6
  refreshToken?: string
@@ -17,10 +15,21 @@ export interface AuthTokenRequest {
17
15
  export interface AuthTokenResponse {
18
16
  accessToken: string
19
17
  tokenType: string
20
- refreshToken: string
18
+ refreshToken?: string
21
19
  expiresIn: number
22
20
  }
23
21
 
22
+ /**
23
+ * This interface is used to request a M2M token as a service
24
+ */
25
+ export interface M2MTokenRequest {
26
+ clientId: string
27
+ clientSecret: string
28
+ requestedScopes: string[]
29
+ practiceUuid: string
30
+ }
31
+
32
+
24
33
  export interface AuthRecoverRequest {
25
34
  practiceUuid: string
26
35
  email: string
@@ -1,7 +1,9 @@
1
1
  export * from './consult'
2
2
  export * from './diagnosis'
3
3
  export * from './error'
4
+ export * from './external'
4
5
  export * from './guard'
6
+ export * from './init'
5
7
  export * from './practice'
6
8
  export * from './shared'
7
9
  export * from './vault'
@@ -0,0 +1,37 @@
1
+ import {
2
+ APIService,
3
+ ConsultService,
4
+ DiagnosisService,
5
+ GuardService,
6
+ PracticeService,
7
+ TellerService,
8
+ VaultService,
9
+ WorkflowService,
10
+ } from '../services'
11
+
12
+ /**
13
+ * This interface represents a collection of service urls you need to initialize
14
+ */
15
+ export interface ServiceCollectionRequest {
16
+ tellerBaseURL?: string
17
+ vaultBaseURL?: string
18
+ guardBaseURL?: string
19
+ practiceBaseURL?: string
20
+ consultBaseURL?: string
21
+ workflowBaseURL?: string
22
+ diagnosisBaseURL?: string
23
+ }
24
+
25
+ /**
26
+ * This interface represents a collection of service
27
+ */
28
+ export interface ServiceCollection {
29
+ apiService: APIService
30
+ tellerService?: TellerService
31
+ practiceService?: PracticeService
32
+ consultService?: ConsultService
33
+ vaultService?: VaultService
34
+ guardService?: GuardService
35
+ workflowService?: WorkflowService
36
+ diagnosisService?: DiagnosisService
37
+ }
@@ -4,11 +4,18 @@ import { AuthRefreshFunc, Tokens } from '../models'
4
4
  import { AxiosService } from './axios'
5
5
  import { GuardRequestConfig } from './guard'
6
6
 
7
-
8
7
  export class APIService extends AxiosService {
9
8
  private authRefreshFn?: AuthRefreshFunc
9
+ private tokens: Tokens = {}
10
10
 
11
+ /**
12
+ * The API Service lets you use an axios API and handles oro backend services authentification via JWT tokens
13
+ * @param useLocalStorage if set to true, tokens will be stored in localStorage
14
+ * @param config (optional) an axios config
15
+ * @param tokenRefreshFailureCallback (optional) callback to call when failing to refresh the auth token
16
+ */
11
17
  constructor(
18
+ private useLocalStorage: boolean,
12
19
  config?: AxiosRequestConfig,
13
20
  private tokenRefreshFailureCallback?: (err: Error) => void
14
21
  ) {
@@ -16,7 +23,7 @@ export class APIService extends AxiosService {
16
23
  const self = this
17
24
 
18
25
  this.axios.interceptors.request.use(
19
- config => {
26
+ (config) => {
20
27
  const token = (config as GuardRequestConfig).useRefreshToken
21
28
  ? self.getTokens().refreshToken
22
29
  : self.getTokens().accessToken
@@ -25,37 +32,40 @@ export class APIService extends AxiosService {
25
32
  ...config.headers,
26
33
  Authorization: `Bearer ${token}`,
27
34
  }
28
- return config;
35
+ return config
29
36
  },
30
- error => {
37
+ (error) => {
31
38
  Promise.reject(error)
32
39
  }
33
- );
40
+ )
34
41
 
35
- createAuthRefreshInterceptor(this.axios, async function (failedRequest) {
36
- if (self.authRefreshFn) {
37
- try {
38
- let tokenResp = await self.authRefreshFn(self.getTokens().refreshToken)
39
- self.setTokens({
40
- accessToken: tokenResp.accessToken,
41
- refreshToken: tokenResp.refreshToken
42
- })
43
- failedRequest.response.config.headers['Authorization'] = `Bearer ${self.getTokens().accessToken}`
44
- return Promise.resolve()
45
-
46
- } catch (e) {
47
- console.error('an error occured while refreshing tokens (notifying callback)', e)
48
- if (self.tokenRefreshFailureCallback)
49
- self.tokenRefreshFailureCallback(failedRequest)
50
- return Promise.resolve() // We keep it like that. Otherwise, it seems to break the api service will it is not needed
51
- // return Promise.reject(e)
42
+ createAuthRefreshInterceptor(
43
+ this.axios,
44
+ async function (failedRequest) {
45
+ if (self.authRefreshFn) {
46
+ try {
47
+ let tokenResp = await self.authRefreshFn(self.getTokens().refreshToken)
48
+ self.setTokens({
49
+ accessToken: tokenResp.accessToken,
50
+ refreshToken: tokenResp.refreshToken,
51
+ })
52
+ failedRequest.response.config.headers['Authorization'] = `Bearer ${
53
+ self.getTokens().accessToken
54
+ }`
55
+ return Promise.resolve()
56
+ } catch (e) {
57
+ console.error('an error occured while refreshing tokens (notifying callback)', e)
58
+ if (self.tokenRefreshFailureCallback) self.tokenRefreshFailureCallback(failedRequest)
59
+ return Promise.resolve() // We keep it like that. Otherwise, it seems to break the api service will it is not needed
60
+ // return Promise.reject(e)
61
+ }
52
62
  }
53
- }
54
- console.error('The request could not refresh the token (authRefreshFn was not set)', failedRequest)
55
- return Promise.resolve() // We keep it like that. Otherwise, it seems to break the api service will it is not needed
56
- // return Promise.reject(failedRequest)
57
-
58
- }, { statusCodes: [401, 403] })
63
+ console.error('The request could not refresh the token (authRefreshFn was not set)', failedRequest)
64
+ return Promise.resolve() // We keep it like that. Otherwise, it seems to break the api service will it is not needed
65
+ // return Promise.reject(failedRequest)
66
+ },
67
+ { statusCodes: [401, 403] }
68
+ )
59
69
  }
60
70
 
61
71
  public setAuthRefreshFn(fn: AuthRefreshFunc) {
@@ -63,15 +73,22 @@ export class APIService extends AxiosService {
63
73
  }
64
74
 
65
75
  public setTokens(tokens: Tokens) {
66
- localStorage.setItem('tokens', JSON.stringify(tokens))
76
+ if (this.useLocalStorage) {
77
+ localStorage.setItem('tokens', JSON.stringify(tokens))
78
+ }
79
+ this.tokens = tokens
67
80
  }
68
81
 
69
82
  public getTokens(): Tokens {
70
- let tokens : Tokens = {}
71
- const item = localStorage.getItem('tokens')
72
- if (item) {
73
- tokens = JSON.parse(item)
83
+ if (this.useLocalStorage) {
84
+ let tokens: Tokens = {}
85
+ const item = localStorage.getItem('tokens')
86
+ if (item) {
87
+ tokens = JSON.parse(item)
88
+ }
89
+ return tokens
90
+ } else {
91
+ return this.tokens
74
92
  }
75
- return tokens
76
93
  }
77
94
  }
@@ -0,0 +1,52 @@
1
+ import { init } from '../helpers'
2
+ import { AuthTokenResponse, ServiceCollection, ServiceCollectionRequest } from '../models'
3
+ import { GuardService } from './guard'
4
+
5
+ /**
6
+ * This service enables you to handle one authentication token per practice
7
+ */
8
+ export class ApisPracticeManager {
9
+ private practiceInstances = new Map<string, ServiceCollection>()
10
+
11
+ /**
12
+ * The constructor
13
+ * @param serviceCollReq the services to initialize. Only filled urls will get corresponding service to be initialized.
14
+ * It will be used each time a new practices needs a `ServiceCollection`
15
+ * @param getAuthTokenCbk the callback function used to get a new JWT token
16
+ * @param useLocalStorage (default: false) if true store tokens into local storage (only for browsers)
17
+ */
18
+ constructor(
19
+ private serviceCollReq: ServiceCollectionRequest,
20
+ private getAuthTokenCbk: (guard: GuardService, practiceUuid: string) => Promise<AuthTokenResponse>,
21
+ private useLocalStorage = false
22
+ ) {}
23
+
24
+ /**
25
+ * This function is used to get a `ServiceCollection` associated to a practice. If missing, it will initialize a new `ServiceCollection`.
26
+ * @param practiceUuid the uuid of the practice
27
+ * @returns a promise holding a `ServiceCollection`
28
+ */
29
+ public async get(practiceUuid: string): Promise<ServiceCollection> {
30
+ const practiceInstance = this.practiceInstances.get(practiceUuid)
31
+ if (practiceInstance) return practiceInstance
32
+
33
+ const newPracticeInstance = init(this.serviceCollReq, undefined, this.useLocalStorage)
34
+
35
+ // Create one auth token callback per practice since the practice uuid needs to change
36
+ const authTokenFunc = async () => {
37
+ if (newPracticeInstance.guardService) {
38
+ console.log(`\x1b[36m[Auth] Refresh auth called (practiceUuid: ${practiceUuid})\x1b[36m`)
39
+ return await this.getAuthTokenCbk(newPracticeInstance.guardService, practiceUuid)
40
+ } else {
41
+ throw Error('[Auth] Unable to refresh token guard service is undefined')
42
+ }
43
+ }
44
+
45
+ // Set the refresh tokens callback
46
+ newPracticeInstance.apiService.setAuthRefreshFn(authTokenFunc)
47
+
48
+ this.practiceInstances.set(practiceUuid, newPracticeInstance)
49
+
50
+ return newPracticeInstance
51
+ }
52
+ }
@@ -16,6 +16,7 @@ import {
16
16
  IdentityResendConfirmEmailRequest,
17
17
  IdentityResponse,
18
18
  IdentityUpdateRequest,
19
+ M2MTokenRequest,
19
20
  QRCodeRequest,
20
21
  QRCodeResponse,
21
22
  Tokens,
@@ -32,7 +33,7 @@ export class GuardService {
32
33
  private whoAmICache: Record<string, WhoAmIResponse>
33
34
 
34
35
  constructor(private api: APIService, private baseURL: string) {
35
- this.api.setAuthRefreshFn(this.authRefresh.bind(this))
36
+ this.api.setAuthRefreshFn(this.authRefresh.bind(this)) // This is the default behavior for User JWT tokens. If you want other kind of refresh you shall overwrite this call
36
37
  this.identityCache = {}
37
38
  this.whoAmICache = {}
38
39
  }
@@ -52,6 +53,46 @@ export class GuardService {
52
53
  this.api.setTokens({ ...this.api.getTokens(), ...tokens })
53
54
  }
54
55
 
56
+ /**
57
+ * Allow to retrieve a M2M token for a service
58
+ *
59
+ * @param req The credentials required to get an access token
60
+ * @returns AuthTokenResponse
61
+ */
62
+ public async m2mToken(req: M2MTokenRequest): Promise<AuthTokenResponse> {
63
+ let resp: AuthTokenResponse | undefined
64
+
65
+ try {
66
+ let config: AxiosAuthRefreshRequestConfig = {
67
+ skipAuthRefresh: true,
68
+ }
69
+
70
+ resp = await this.api.post<AuthTokenResponse>(`${this.baseURL}/v1/m2m/token`, req, config)
71
+
72
+ this.api.setTokens({
73
+ accessToken: resp.accessToken,
74
+ })
75
+ } catch (e) {
76
+ console.error('Error while posting m2m token:', e)
77
+
78
+ if ((e as any).isAxiosError) {
79
+ const code = (e as AxiosError).response?.status
80
+ switch (code) {
81
+ case 400:
82
+ throw new AuthenticationBadRequest()
83
+ case 500:
84
+ throw new AuthenticationServerError()
85
+ case 401:
86
+ default:
87
+ throw new AuthenticationFailed()
88
+ }
89
+ }
90
+ throw new AuthenticationFailed()
91
+ }
92
+
93
+ return resp
94
+ }
95
+
55
96
  /**
56
97
  * Allow to retrieve an access token and a refresh token in order
57
98
  * to do authenticated request afterward
@@ -74,6 +115,8 @@ export class GuardService {
74
115
  refreshToken: resp.refreshToken,
75
116
  })
76
117
  } catch (e) {
118
+ console.error('Error while posting auth token:', e)
119
+
77
120
  if ((e as any).isAxiosError) {
78
121
  const code = (e as AxiosError).response?.status
79
122
  switch (code) {
@@ -237,7 +280,7 @@ export class GuardService {
237
280
  * @returns IdentityResponse
238
281
  */
239
282
  public async identityGetByCustomerEmail(email: string): Promise<IdentityResponse> {
240
- return this.identityGetByHash(email.substring(email.indexOf('+')+1, email.indexOf('@')))
283
+ return this.identityGetByHash(email.substring(email.indexOf('+') + 1, email.indexOf('@')))
241
284
  }
242
285
 
243
286
  /**
@@ -252,9 +295,8 @@ export class GuardService {
252
295
  //call (ie: /v1/mapping/[b64Hash]) which would return a blob to decrypt
253
296
  //which would contain the real identityID to call IdentityGet with.
254
297
 
255
-
256
298
  //The hash comes in base64 format but it isn't URL safe soe we have to convert
257
299
  //to base64URL (see https://en.wikipedia.org/wiki/Base64#The_URL_applications)
258
- return this.identityGet(b64Hash.replace('+','-').replace('/','_'))
300
+ return this.identityGet(b64Hash.replace('+', '-').replace('/', '_'))
259
301
  }
260
302
  }
@@ -1,4 +1,5 @@
1
1
  export * from './api'
2
+ export * from './apisPracticeManager'
2
3
  export * from './axios'
3
4
  export * from './consult'
4
5
  export * from './diagnosis'
@@ -1,5 +1,4 @@
1
- import { Buffer } from 'buffer/'
2
- import { sha256 } from 'hash.js'
1
+ import { hashToBase64String } from '../helpers'
3
2
  import { ConsultRequestMetadata, PracticeAccount, Uuid } from '../models'
4
3
  import {
5
4
  Assignment,
@@ -175,7 +174,7 @@ export class PracticeService {
175
174
  * @returns a hashed email
176
175
  */
177
176
  public getPaymentIntentHashedEmail(email: string): string {
178
- return Buffer.from(sha256().update(email.toLowerCase()).digest('hex'), 'hex').toString('base64')
177
+ return hashToBase64String(email.toLowerCase())
179
178
  }
180
179
 
181
180
  /**