@authorizerdev/authorizer-js 0.2.1 → 0.4.0-beta.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.
package/src/index.ts CHANGED
@@ -1,151 +1,30 @@
1
1
  // Note: write gql query in single line to reduce bundle size
2
2
  import nodeFetch from 'node-fetch';
3
-
4
- export type ConfigType = {
5
- authorizerURL: string;
6
- redirectURL: string;
7
- };
8
-
9
- export type User = {
10
- id: string;
11
- email: string;
12
- preferred_username: string;
13
- email_verified: boolean;
14
- signup_methods: string;
15
- given_name?: string | null;
16
- family_name?: string | null;
17
- middle_name?: string | null;
18
- picture?: string | null;
19
- gender?: string | null;
20
- birthdate?: string | null;
21
- phone_number?: string | null;
22
- phone_number_verified?: boolean | null;
23
- roles?: string[];
24
- created_at: number;
25
- updated_at: number;
26
- };
27
-
28
- export type AuthToken = {
29
- message?: string;
30
- access_token: string;
31
- expires_at: number;
32
- user?: User;
33
- };
34
-
35
- export type Response = {
36
- message: string;
37
- };
38
-
39
- export type Headers = Record<string, string>;
40
-
41
- export type LoginInput = { email: string; password: string; roles?: string[] };
42
-
43
- export type SignupInput = {
44
- email: string;
45
- password: string;
46
- confirm_password: string;
47
- given_name?: string;
48
- family_name?: string;
49
- middle_name?: string;
50
- picture?: string;
51
- gender?: string;
52
- birthdate?: string;
53
- phone_number?: string;
54
- roles?: string[];
55
- };
56
-
57
- export type MagicLinkLoginInput = {
58
- email: string;
59
- roles?: string[];
60
- };
61
-
62
- export type VerifyEmailInput = { token: string };
63
-
64
- export type GraphqlQueryInput = {
65
- query: string;
66
- variables?: Record<string, any>;
67
- headers?: Headers;
68
- };
69
-
70
- export type MetaData = {
71
- version: string;
72
- is_google_login_enabled: boolean;
73
- is_facebook_login_enabled: boolean;
74
- is_github_login_enabled: boolean;
75
- is_email_verification_enabled: boolean;
76
- is_basic_authentication_enabled: boolean;
77
- is_magic_link_login_enabled: boolean;
78
- };
79
-
80
- export type UpdateProfileInput = {
81
- old_password?: string;
82
- new_password?: string;
83
- confirm_new_password?: string;
84
- email?: string;
85
- given_name?: string;
86
- family_name?: string;
87
- middle_name?: string;
88
- nickname?: string;
89
- gender?: string;
90
- birthdate?: string;
91
- phone_number?: string;
92
- picture?: string;
93
- };
94
-
95
- export type ForgotPasswordInput = {
96
- email: string;
97
- };
98
-
99
- export type ResetPasswordInput = {
100
- token: string;
101
- password: string;
102
- confirm_password: string;
103
- };
104
-
105
- export type SessionQueryInput = {
106
- roles?: string[];
107
- };
108
-
109
- export type IsValidJWTQueryInput = {
110
- jwt: string;
111
- roles?: string[];
112
- };
113
-
114
- export type ValidJWTResponse = {
115
- valid: string;
116
- message: string;
117
- };
118
-
119
- export enum OAuthProviders {
120
- Github = 'github',
121
- Google = 'google',
122
- Facebook = 'facebook',
123
- }
124
-
125
- const hasWindow = (): boolean => typeof window !== 'undefined';
3
+ import { DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS } from './constants';
4
+ import * as Types from './types';
5
+ import {
6
+ trimURL,
7
+ hasWindow,
8
+ encode,
9
+ createRandomString,
10
+ sha256,
11
+ bufferToBase64UrlEncoded,
12
+ createQueryParams,
13
+ executeIframe,
14
+ } from './utils';
126
15
 
127
16
  // re-usable gql response fragment
128
17
  const userFragment = `id email email_verified given_name family_name middle_name nickname preferred_username picture signup_methods gender birthdate phone_number phone_number_verified roles created_at updated_at `;
129
- const authTokenFragment = `message access_token expires_at user { ${userFragment} }`;
130
-
131
- const trimURL = (url: string): string => {
132
- let trimmedData = url.trim();
133
- const lastChar = trimmedData[trimmedData.length - 1];
134
- if (lastChar === '/') {
135
- trimmedData = trimmedData.slice(0, -1);
136
- } else {
137
- trimmedData = trimmedData;
138
- }
139
-
140
- return trimmedData;
141
- };
18
+ const authTokenFragment = `message access_token expires_in refresh_token id_token user { ${userFragment} }`;
142
19
 
20
+ export * from './types';
143
21
  export class Authorizer {
144
22
  // class variable
145
- config: ConfigType;
23
+ config: Types.ConfigType;
24
+ codeVerifier: string;
146
25
 
147
26
  // constructor
148
- constructor(config: ConfigType) {
27
+ constructor(config: Types.ConfigType) {
149
28
  if (!config) {
150
29
  throw new Error(`Configuration is required`);
151
30
  }
@@ -161,11 +40,103 @@ export class Authorizer {
161
40
  } else {
162
41
  this.config.redirectURL = trimURL(config.redirectURL);
163
42
  }
43
+
44
+ this.config.clientID = config.clientID.trim();
164
45
  }
165
46
 
47
+ getToken = async (data: { code?: string }) => {
48
+ if (!this.codeVerifier) {
49
+ throw new Error(`invalid code verifier`);
50
+ }
51
+
52
+ const requestData = {
53
+ client_id: this.config.clientID,
54
+ code: data.code,
55
+ code_verifier: this.codeVerifier,
56
+ };
57
+
58
+ try {
59
+ const res = await fetch(`${this.config.authorizerURL}/oauth/token`, {
60
+ method: 'POST',
61
+ body: JSON.stringify(requestData),
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ credentials: 'include',
66
+ });
67
+
68
+ const json = await res.json();
69
+ if (res.status >= 400) {
70
+ throw new Error(json);
71
+ }
72
+ return json;
73
+ } catch (err) {
74
+ throw err;
75
+ }
76
+ };
77
+
78
+ authorize = async (data: Types.AuthorizeInput) => {
79
+ if (!hasWindow) {
80
+ throw new Error(`this feature is only supported in browser`);
81
+ }
82
+
83
+ const scopes = ['openid', 'profile', 'email'];
84
+ if (data.use_refresh_token) {
85
+ scopes.push('offline_access');
86
+ }
87
+
88
+ const requestData: Record<string, string> = {
89
+ redirect_uri: this.config.redirectURL,
90
+ response_mode: data.response_mode || 'web_message',
91
+ state: encode(createRandomString()),
92
+ nonce: encode(createRandomString()),
93
+ response_type: data.response_type,
94
+ scope: scopes.join(' '),
95
+ client_id: this.config.clientID,
96
+ };
97
+
98
+ if (data.response_type === Types.ResponseTypes.Code) {
99
+ this.codeVerifier = createRandomString();
100
+ const sha = await sha256(this.codeVerifier);
101
+ const codeChallenge = bufferToBase64UrlEncoded(sha);
102
+ requestData.code_challenge = codeChallenge;
103
+ }
104
+
105
+ const authorizeURL = `${
106
+ this.config.authorizerURL
107
+ }/authorize?${createQueryParams(requestData)}`;
108
+
109
+ try {
110
+ const iframeRes = await executeIframe(
111
+ authorizeURL,
112
+ this.config.authorizerURL,
113
+ DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
114
+ );
115
+
116
+ if (data.response_type === Types.ResponseTypes.Code) {
117
+ // get token and return it
118
+ const token = await this.getToken({ code: iframeRes.code });
119
+ return token;
120
+ }
121
+
122
+ // this includes access_token, id_token & refresh_token(optionally)
123
+ return iframeRes;
124
+ } catch (err) {
125
+ if (err.error) {
126
+ window.location.replace(
127
+ `${this.config.authorizerURL}/app?state=${encode(
128
+ JSON.stringify(this.config),
129
+ )}`,
130
+ );
131
+ }
132
+
133
+ throw err;
134
+ }
135
+ };
136
+
166
137
  // helper to execute graphql queries
167
138
  // takes in any query or mutation string as input
168
- graphqlQuery = async (data: GraphqlQueryInput) => {
139
+ graphqlQuery = async (data: Types.GraphqlQueryInput) => {
169
140
  // set fetch based on window object. Isomorphic fetch doesn't support credentail: true
170
141
  // hence cookie based auth might not work so it is imp to use window.fetch in that case
171
142
  const f = hasWindow() ? window.fetch : nodeFetch;
@@ -192,7 +163,7 @@ export class Authorizer {
192
163
  return json.data;
193
164
  };
194
165
 
195
- getMetaData = async (): Promise<MetaData | void> => {
166
+ getMetaData = async (): Promise<Types.MetaData | void> => {
196
167
  try {
197
168
  const res = await this.graphqlQuery({
198
169
  query: `query { meta { version is_google_login_enabled is_facebook_login_enabled is_github_login_enabled is_email_verification_enabled is_basic_authentication_enabled is_magic_link_login_enabled } }`,
@@ -206,9 +177,9 @@ export class Authorizer {
206
177
 
207
178
  // this is used to verify / get session using cookie by default. If using nodejs pass authorization header
208
179
  getSession = async (
209
- headers?: Headers,
210
- params?: SessionQueryInput,
211
- ): Promise<AuthToken> => {
180
+ headers?: Types.Headers,
181
+ params?: Types.SessionQueryInput,
182
+ ): Promise<Types.AuthToken> => {
212
183
  try {
213
184
  const res = await this.graphqlQuery({
214
185
  query: `query getSession($params: SessionQueryInput){session(params: $params) { ${authTokenFragment} } }`,
@@ -223,26 +194,18 @@ export class Authorizer {
223
194
  }
224
195
  };
225
196
 
226
- isValidJWT = async (data: {
227
- jwt: string;
228
- roles?: string[];
229
- }): Promise<ValidJWTResponse> => {
197
+ magicLinkLogin = async (
198
+ data: Types.MagicLinkLoginInput,
199
+ ): Promise<Response> => {
230
200
  try {
231
- const res = await this.graphqlQuery({
232
- query: `query isValidJWT($params: IsValidJWTQueryInput){ is_valid_jwt(params: $params) { message valid } }`,
233
- variables: {
234
- params: data,
235
- },
236
- });
201
+ if (!data.state) {
202
+ data.state = encode(createRandomString());
203
+ }
237
204
 
238
- return res.is_valid_jwt;
239
- } catch (err) {
240
- throw err;
241
- }
242
- };
205
+ if (!data.redirect_uri) {
206
+ data.redirect_uri = this.config.redirectURL;
207
+ }
243
208
 
244
- magicLinkLogin = async (data: MagicLinkLoginInput): Promise<Response> => {
245
- try {
246
209
  const res = await this.graphqlQuery({
247
210
  query: `
248
211
  mutation magicLinkLogin($data: MagicLinkLoginInput!) { magic_link_login(params: $data) { message }}
@@ -256,7 +219,7 @@ export class Authorizer {
256
219
  }
257
220
  };
258
221
 
259
- signup = async (data: SignupInput): Promise<AuthToken | void> => {
222
+ signup = async (data: Types.SignupInput): Promise<Types.AuthToken | void> => {
260
223
  try {
261
224
  const res = await this.graphqlQuery({
262
225
  query: `
@@ -271,7 +234,9 @@ export class Authorizer {
271
234
  }
272
235
  };
273
236
 
274
- verifyEmail = async (data: VerifyEmailInput): Promise<AuthToken | void> => {
237
+ verifyEmail = async (
238
+ data: Types.VerifyEmailInput,
239
+ ): Promise<Types.AuthToken | void> => {
275
240
  try {
276
241
  const res = await this.graphqlQuery({
277
242
  query: `
@@ -286,7 +251,7 @@ export class Authorizer {
286
251
  }
287
252
  };
288
253
 
289
- login = async (data: LoginInput): Promise<AuthToken | void> => {
254
+ login = async (data: Types.LoginInput): Promise<Types.AuthToken | void> => {
290
255
  try {
291
256
  const res = await this.graphqlQuery({
292
257
  query: `
@@ -301,7 +266,7 @@ export class Authorizer {
301
266
  }
302
267
  };
303
268
 
304
- getProfile = async (headers?: Headers): Promise<User | void> => {
269
+ getProfile = async (headers?: Types.Headers): Promise<Types.User | void> => {
305
270
  try {
306
271
  const profileRes = await this.graphqlQuery({
307
272
  query: `query { profile { ${userFragment} } }`,
@@ -315,9 +280,9 @@ export class Authorizer {
315
280
  };
316
281
 
317
282
  updateProfile = async (
318
- data: UpdateProfileInput,
319
- headers?: Headers,
320
- ): Promise<Response | void> => {
283
+ data: Types.UpdateProfileInput,
284
+ headers?: Types.Headers,
285
+ ): Promise<Types.Response | void> => {
321
286
  try {
322
287
  const updateProfileRes = await this.graphqlQuery({
323
288
  query: `mutation updateProfile($data: UpdateProfileInput!) { update_profile(params: $data) { message } }`,
@@ -334,8 +299,16 @@ export class Authorizer {
334
299
  };
335
300
 
336
301
  forgotPassword = async (
337
- data: ForgotPasswordInput,
338
- ): Promise<Response | void> => {
302
+ data: Types.ForgotPasswordInput,
303
+ ): Promise<Types.Response | void> => {
304
+ if (!data.state) {
305
+ data.state = encode(createRandomString());
306
+ }
307
+
308
+ if (!data.redirect_uri) {
309
+ data.redirect_uri = this.config.redirectURL;
310
+ }
311
+
339
312
  try {
340
313
  const forgotPasswordRes = await this.graphqlQuery({
341
314
  query: `mutation forgotPassword($data: ForgotPasswordInput!) { forgot_password(params: $data) { message } }`,
@@ -351,8 +324,8 @@ export class Authorizer {
351
324
  };
352
325
 
353
326
  resetPassword = async (
354
- data: ResetPasswordInput,
355
- ): Promise<Response | void> => {
327
+ data: Types.ResetPasswordInput,
328
+ ): Promise<Types.Response | void> => {
356
329
  try {
357
330
  const resetPasswordRes = await this.graphqlQuery({
358
331
  query: `mutation resetPassword($data: ResetPasswordInput!) { reset_password(params: $data) { message } }`,
@@ -366,7 +339,7 @@ export class Authorizer {
366
339
  }
367
340
  };
368
341
 
369
- browserLogin = async (): Promise<AuthToken | void> => {
342
+ browserLogin = async (): Promise<Types.AuthToken | void> => {
370
343
  try {
371
344
  const token = await this.getSession();
372
345
  return token;
@@ -375,7 +348,7 @@ export class Authorizer {
375
348
  throw new Error(`browserLogin is only supported for browsers`);
376
349
  }
377
350
  window.location.replace(
378
- `${this.config.authorizerURL}/app?state=${btoa(
351
+ `${this.config.authorizerURL}/app?state=${encode(
379
352
  JSON.stringify(this.config),
380
353
  )}`,
381
354
  );
@@ -404,7 +377,7 @@ export class Authorizer {
404
377
  );
405
378
  };
406
379
 
407
- logout = async (headers?: Headers): Promise<Response | void> => {
380
+ logout = async (headers?: Types.Headers): Promise<Types.Response | void> => {
408
381
  try {
409
382
  const res = await this.graphqlQuery({
410
383
  query: ` mutation { logout { message } } `,
@@ -412,7 +385,6 @@ export class Authorizer {
412
385
  });
413
386
  return res.logout;
414
387
  } catch (err) {
415
- console.log(`logout err:`, err);
416
388
  console.error(err);
417
389
  }
418
390
  };
package/src/types.ts ADDED
@@ -0,0 +1,147 @@
1
+ export type ConfigType = {
2
+ authorizerURL: string;
3
+ redirectURL: string;
4
+ clientID: string;
5
+ };
6
+
7
+ export type User = {
8
+ id: string;
9
+ email: string;
10
+ preferred_username: string;
11
+ email_verified: boolean;
12
+ signup_methods: string;
13
+ given_name?: string | null;
14
+ family_name?: string | null;
15
+ middle_name?: string | null;
16
+ picture?: string | null;
17
+ gender?: string | null;
18
+ birthdate?: string | null;
19
+ phone_number?: string | null;
20
+ phone_number_verified?: boolean | null;
21
+ roles?: string[];
22
+ created_at: number;
23
+ updated_at: number;
24
+ };
25
+
26
+ export type AuthToken = {
27
+ message?: string;
28
+ access_token: string;
29
+ expires_in: number;
30
+ id_token: string;
31
+ refresh_token?: string;
32
+ user?: User;
33
+ };
34
+
35
+ export type Response = {
36
+ message: string;
37
+ };
38
+
39
+ export type Headers = Record<string, string>;
40
+
41
+ export type LoginInput = { email: string; password: string; roles?: string[] };
42
+
43
+ export type SignupInput = {
44
+ email: string;
45
+ password: string;
46
+ confirm_password: string;
47
+ given_name?: string;
48
+ family_name?: string;
49
+ middle_name?: string;
50
+ picture?: string;
51
+ gender?: string;
52
+ birthdate?: string;
53
+ phone_number?: string;
54
+ roles?: string[];
55
+ };
56
+
57
+ export type MagicLinkLoginInput = {
58
+ email: string;
59
+ roles?: string[];
60
+ scopes?: string[];
61
+ state?: string;
62
+ redirect_uri?: string;
63
+ };
64
+
65
+ export type VerifyEmailInput = { token: string };
66
+
67
+ export type GraphqlQueryInput = {
68
+ query: string;
69
+ variables?: Record<string, any>;
70
+ headers?: Headers;
71
+ };
72
+
73
+ export type MetaData = {
74
+ version: string;
75
+ client_id: string;
76
+ is_google_login_enabled: boolean;
77
+ is_facebook_login_enabled: boolean;
78
+ is_github_login_enabled: boolean;
79
+ is_email_verification_enabled: boolean;
80
+ is_basic_authentication_enabled: boolean;
81
+ is_magic_link_login_enabled: boolean;
82
+ };
83
+
84
+ export type UpdateProfileInput = {
85
+ old_password?: string;
86
+ new_password?: string;
87
+ confirm_new_password?: string;
88
+ email?: string;
89
+ given_name?: string;
90
+ family_name?: string;
91
+ middle_name?: string;
92
+ nickname?: string;
93
+ gender?: string;
94
+ birthdate?: string;
95
+ phone_number?: string;
96
+ picture?: string;
97
+ };
98
+
99
+ export type ForgotPasswordInput = {
100
+ email: string;
101
+ state?: string;
102
+ redirect_uri?: string;
103
+ };
104
+
105
+ export type ResetPasswordInput = {
106
+ token: string;
107
+ password: string;
108
+ confirm_password: string;
109
+ };
110
+
111
+ export type SessionQueryInput = {
112
+ roles?: string[];
113
+ };
114
+
115
+ export type IsValidJWTQueryInput = {
116
+ jwt: string;
117
+ roles?: string[];
118
+ };
119
+
120
+ export type ValidJWTResponse = {
121
+ valid: string;
122
+ message: string;
123
+ };
124
+
125
+ export enum OAuthProviders {
126
+ Github = 'github',
127
+ Google = 'google',
128
+ Facebook = 'facebook',
129
+ }
130
+
131
+ export enum ResponseTypes {
132
+ Code = 'code',
133
+ Token = 'token',
134
+ }
135
+
136
+ export type AuthorizeInput = {
137
+ response_type: ResponseTypes;
138
+ use_refresh_token?: boolean;
139
+ response_mode?: string;
140
+ };
141
+
142
+ export type AuthorizeResponse = {
143
+ state: string;
144
+ code?: string;
145
+ error?: string;
146
+ error_description?: string;
147
+ };