/
OtpProvider.ts
196 lines (176 loc) · 5.83 KB
/
OtpProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { AuthMethodType } from '@lit-protocol/constants';
import {
AuthMethod,
AuthenticateOptions,
BaseProviderOptions,
OtpAuthenticateOptions,
SignInWithOTPParams,
} from '@lit-protocol/types';
import { BaseProvider } from './BaseProvider';
import { OtpProviderOptions } from '@lit-protocol/types';
import { ethers } from 'ethers';
export class OtpProvider extends BaseProvider {
private _params: SignInWithOTPParams;
private _baseUrl: string; // TODO: REMOVE THIS HARD CODED STRING
private _port: string;
private _startRoute: string;
private _checkRoute: string;
private _requestId: string = '';
constructor(
params: BaseProviderOptions & SignInWithOTPParams,
config?: OtpProviderOptions
) {
super(params);
this._params = params;
this._baseUrl = config?.baseUrl || 'https://auth-api.litgateway.com';
this._port = config?.port || '443';
this._startRoute = config?.startRoute || '/api/otp/start';
this._checkRoute = config?.checkRoute || '/api/otp/check';
}
/**
* Validates OTP code from {@link sendOtpCode}
* @param options {T extends AuthenticateOptions} options used in authentication
* @returns {Promise<AuthMethod>} Auth Method object containing Json Web Token
*/
public async authenticate<T extends AuthenticateOptions>(
options?: T
): Promise<AuthMethod> {
if (options) {
return this.checkOtpCode(
(options as unknown as OtpAuthenticateOptions).code
);
} else {
throw new Error(
`Must provide authentication options for OTP check options given are: ${options}`
);
}
}
/**
* Starts an otp session for a given email or phone number from the {@link SignInWithOTPParams}
* @returns {Promise<string>} returns a callback to check status of the verification session if successful
*/
public async sendOtpCode(): Promise<string> {
const url = this._buildUrl('start');
this._requestId =
this._params.requestId ??
(Math.random() * 10000 + 1).toString(10).replace('.', '');
let body: any = {
otp: this._params.userId,
request_id: this._requestId,
};
if (this._params.emailCustomizationOptions) {
body.email_configuration = {};
body.email_configuration.from_name =
this._params.emailCustomizationOptions.fromName;
if (this._params.emailCustomizationOptions.from)
body.email_configuration.from =
this._params.emailCustomizationOptions.from;
}
if (this._params.customName) body.custom_name = this._params.customName;
body = JSON.stringify(body);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
'api-key': '67e55044-10b1-426f-9247-bb680e5fe0c8_JsSdk',
},
body,
});
if (response.status < 200 || response.status >= 400) {
console.warn('Something wrong with OTP request', await response.json());
const err = new Error('Unable to start otp verification');
throw err;
}
let respBody: { status: string; callback: string } = await response.json();
return respBody.callback;
}
/**
* Validates otp code from {@link sendOtpCode}
*
* @param code {string} - OTP code sent to the user, should be retrieved from user input.
* @returns {Promise<AuthMethod} - Auth method that contains Json Web Token
*/
private async checkOtpCode(code: string): Promise<AuthMethod> {
const url = this._buildUrl('check');
/**
pub struct OtpCheckRequest {
pub otp: String,
pub code: String,
pub request_id: String,
}
*/
let body: any = {
otp: this._params.userId,
code,
request_id: this._requestId,
};
body = JSON.stringify(body);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
'api-key': '67e55044-10b1-426f-9247-bb680e5fe0c8_JsSdk',
},
body,
});
if (response.status < 200 || response.status >= 400) {
console.warn('Something wrong with OTP request', await response.json());
const err = new Error('unsucessful otp check');
throw err;
}
const respBody: any = await response.json();
if (!respBody.token_jwt) {
throw new Error('Invalid otp code, operation was aborted');
}
return {
accessToken: respBody.token_jwt,
authMethodType: AuthMethodType.OTP,
};
}
private _buildUrl(route: string): string {
switch (route) {
case 'start':
return `${this._baseUrl}:${this._port}${this._startRoute}`;
case 'check':
return `${this._baseUrl}:${this._port}${this._checkRoute}`;
default:
return '';
}
}
/**
* Get auth method id that can be used to look up and interact with
* PKPs associated with the given auth method
*
* @param {AuthMethod} authMethod - Auth method object
*
* @returns {Promise<string>} - Auth method id
*/
public async getAuthMethodId(authMethod: AuthMethod): Promise<string> {
const tokenBody = this.#parseJWT(authMethod.accessToken);
const message: string = tokenBody['extraData'] as string;
const contents = message.split('|');
const userId = contents[0];
const orgId = (tokenBody['orgId'] as string).toLowerCase();
const authMethodId = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(`${userId}:${orgId}`)
);
return authMethodId;
}
/**
* Parse OTP token
*
* @param {string} jwt - Token to parse
* @returns {Record<string, unknown>} - Parsed body
*/
#parseJWT(jwt: string): Record<string, unknown> {
let parts = jwt.split('.');
if (parts.length !== 3) {
throw new Error('Invalid token length');
}
let body = Buffer.from(parts[1], 'base64');
let parsedBody: Record<string, unknown> = JSON.parse(
body.toString('ascii')
);
return parsedBody;
}
}