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