From 85f8e21d5d3639782a1ec88c828828fdc9fb5d01 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 30 Dec 2025 20:15:51 +0530 Subject: [PATCH 1/5] chore: update types and apis as v2 of authorizer --- package.json | 13 +- src/index.ts | 138 ++++++++-------- src/types.ts | 427 ++++++++++++++++++++++++++++++++----------------- tsup.config.ts | 15 +- 4 files changed, 360 insertions(+), 233 deletions(-) diff --git a/package.json b/package.json index be9975a..acd0678 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,11 @@ "node": ">=16" }, "scripts": { - "start": "rollup -w --config rollup.test.config.js", - "ts-types": "tsc --emitDeclarationOnly --outDir lib", + "start": "tsup --watch", "build": "tsup", - "test": "npm run build && jest --testTimeout=500000 --runInBand", + "test": "pnpm run build && jest --testTimeout=500000 --runInBand", "prepare": "husky install", - "prepublishOnly": "npm run build", + "prepublishOnly": "pnpm run build", "release": "pnpm build && bumpp --commit --push --tag && pnpm publish", "release-beta": "pnpm build && bumpp --commit --push --tag && pnpm publish --tag beta", "lint": "eslint --ignore-pattern 'tsup.config.ts' --ext .ts,.tsx,.js,.jsx,.json .", @@ -54,9 +53,6 @@ }, "devDependencies": { "@antfu/eslint-config": "^2.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.5", "@swc/core": "^1.3.99", "@types/jest": "^29.5.12", "@types/node": "^20.9.4", @@ -67,9 +63,6 @@ "husky": "^8.0.0", "jest": "^29.7.0", "lint-staged": "^15.2.0", - "rollup": "^2.79.1", - "rollup-plugin-filesize": "^10.0.0", - "rollup-plugin-serve": "^2.0.2", "testcontainers": "^10.3.2", "ts-jest": "^29.1.1", "tslib": "^2.6.2", diff --git a/src/index.ts b/src/index.ts index 2c84396..938139e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,25 +12,10 @@ import { sha256, trimURL, } from './utils'; -import type { - ApiResponse, - AuthToken, - AuthorizeResponse, - ConfigType, - GenericResponse, - GetTokenResponse, - GrapQlResponseType, - MetaData, - ResendVerifyEmailInput, - User, - ValidateJWTTokenResponse, - ValidateSessionResponse, - ForgotPasswordResponse, -} from './types'; // re-usable gql response fragment 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 is_multi_factor_auth_enabled app_data'; + '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 revoked_timestamp is_multi_factor_auth_enabled app_data'; const authTokenFragment = `message access_token expires_in refresh_token id_token should_show_email_otp_screen should_show_mobile_otp_screen should_show_totp_screen authenticator_scanner_image authenticator_secret authenticator_recovery_codes user { ${userFragment} }`; // set fetch based on window object. Cross fetch have issues with umd build @@ -40,11 +25,11 @@ export * from './types'; export class Authorizer { // class variable - config: ConfigType; + config: Types.ConfigType; codeVerifier: string; // constructor - constructor(config: ConfigType) { + constructor(config: Types.ConfigType) { if (!config) throw new Error('Configuration is required'); this.config = config; @@ -68,9 +53,10 @@ export class Authorizer { } authorize = async ( - data: Types.AuthorizeInput, + data: Types.AuthorizeRequest, ): Promise< - ApiResponse | ApiResponse + | Types.ApiResponse + | Types.ApiResponse > => { if (!hasWindow()) return this.errorResponse([ @@ -115,9 +101,10 @@ export class Authorizer { if (data.response_type === Types.ResponseTypes.Code) { // get token and return it - const tokenResp: ApiResponse = await this.getToken({ - code: iframeRes.code, - }); + const tokenResp: Types.ApiResponse = + await this.getToken({ + code: iframeRes.code, + }); return tokenResp.errors.length ? this.errorResponse(tokenResp.errors) : this.okResponse(tokenResp.data); @@ -138,9 +125,10 @@ export class Authorizer { } }; - browserLogin = async (): Promise> => { + browserLogin = async (): Promise> => { try { - const tokenResp: ApiResponse = await this.getSession(); + const tokenResp: Types.ApiResponse = + await this.getSession(); return tokenResp.errors.length ? this.errorResponse(tokenResp.errors) : this.okResponse(tokenResp.data); @@ -162,8 +150,8 @@ export class Authorizer { }; forgotPassword = async ( - data: Types.ForgotPasswordInput, - ): Promise> => { + data: Types.ForgotPasswordRequest, + ): Promise> => { if (!data.state) data.state = encode(createRandomString()); if (!data.redirect_uri) data.redirect_uri = this.config.redirectURL; @@ -171,7 +159,7 @@ export class Authorizer { try { const forgotPasswordResp = await this.graphqlQuery({ query: - 'mutation forgotPassword($data: ForgotPasswordInput!) { forgot_password(params: $data) { message should_show_mobile_otp_screen } }', + 'mutation forgotPassword($data: ForgotPasswordRequest!) { forgot_password(params: $data) { message should_show_mobile_otp_screen } }', variables: { data, }, @@ -184,7 +172,7 @@ export class Authorizer { } }; - getMetaData = async (): Promise> => { + getMetaData = async (): Promise> => { try { const res = await this.graphqlQuery({ query: @@ -199,7 +187,9 @@ export class Authorizer { } }; - getProfile = async (headers?: Types.Headers): Promise> => { + getProfile = async ( + headers?: Types.Headers, + ): Promise> => { try { const profileRes = await this.graphqlQuery({ query: `query { profile { ${userFragment} } }`, @@ -217,11 +207,11 @@ export class Authorizer { // this is used to verify / get session using cookie by default. If using node.js pass authorization header getSession = async ( headers?: Types.Headers, - params?: Types.SessionQueryInput, - ): Promise> => { + params?: Types.SessionQueryRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ - query: `query getSession($params: SessionQueryInput){session(params: $params) { ${authTokenFragment} } }`, + query: `query getSession($params: SessionQueryRequest){session(params: $params) { ${authTokenFragment} } }`, headers, variables: { params, @@ -236,8 +226,8 @@ export class Authorizer { }; getToken = async ( - data: Types.GetTokenInput, - ): Promise> => { + data: Types.GetTokenRequest, + ): Promise> => { if (!data.grant_type) data.grant_type = 'authorization_code'; if (data.grant_type === 'refresh_token' && !data.refresh_token) @@ -277,11 +267,13 @@ export class Authorizer { } }; - login = async (data: Types.LoginInput): Promise> => { + login = async ( + data: Types.LoginRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` - mutation login($data: LoginInput!) { login(params: $data) { ${authTokenFragment}}} + mutation login($data: LoginRequest!) { login(params: $data) { ${authTokenFragment}}} `, variables: { data }, }); @@ -296,7 +288,7 @@ export class Authorizer { logout = async ( headers?: Types.Headers, - ): Promise> => { + ): Promise> => { try { const res = await this.graphqlQuery({ query: ' mutation { logout { message } } ', @@ -311,8 +303,8 @@ export class Authorizer { }; magicLinkLogin = async ( - data: Types.MagicLinkLoginInput, - ): Promise> => { + data: Types.MagicLinkLoginRequest, + ): Promise> => { try { if (!data.state) data.state = encode(createRandomString()); @@ -320,7 +312,7 @@ export class Authorizer { const res = await this.graphqlQuery({ query: ` - mutation magicLinkLogin($data: MagicLinkLoginInput!) { magic_link_login(params: $data) { message }} + mutation magicLinkLogin($data: MagicLinkLoginRequest!) { magic_link_login(params: $data) { message }} `, variables: { data }, }); @@ -365,8 +357,8 @@ export class Authorizer { }; resendOtp = async ( - data: Types.ResendOtpInput, - ): Promise> => { + data: Types.ResendOtpRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` @@ -384,12 +376,12 @@ export class Authorizer { }; resetPassword = async ( - data: Types.ResetPasswordInput, - ): Promise> => { + data: Types.ResetPasswordRequest, + ): Promise> => { try { const resetPasswordRes = await this.graphqlQuery({ query: - 'mutation resetPassword($data: ResetPasswordInput!) { reset_password(params: $data) { message } }', + 'mutation resetPassword($data: ResetPasswordRequest!) { reset_password(params: $data) { message } }', variables: { data, }, @@ -422,11 +414,13 @@ export class Authorizer { return this.okResponse(responseData); }; - signup = async (data: Types.SignupInput): Promise> => { + signup = async ( + data: Types.SignUpRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` - mutation signup($data: SignUpInput!) { signup(params: $data) { ${authTokenFragment}}} + mutation signup($data: SignUpRequest!) { signup(params: $data) { ${authTokenFragment}}} `, variables: { data }, }); @@ -440,13 +434,13 @@ export class Authorizer { }; updateProfile = async ( - data: Types.UpdateProfileInput, + data: Types.UpdateProfileRequest, headers?: Types.Headers, - ): Promise> => { + ): Promise> => { try { const updateProfileRes = await this.graphqlQuery({ query: - 'mutation updateProfile($data: UpdateProfileInput!) { update_profile(params: $data) { message } }', + 'mutation updateProfile($data: UpdateProfileRequest!) { update_profile(params: $data) { message } }', headers, variables: { data, @@ -463,7 +457,7 @@ export class Authorizer { deactivateAccount = async ( headers?: Types.Headers, - ): Promise> => { + ): Promise> => { try { const res = await this.graphqlQuery({ query: 'mutation deactivateAccount { deactivate_account { message } }', @@ -478,12 +472,12 @@ export class Authorizer { }; validateJWTToken = async ( - params?: Types.ValidateJWTTokenInput, - ): Promise> => { + params?: Types.ValidateJWTTokenRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: - 'query validateJWTToken($params: ValidateJWTTokenInput!){validate_jwt_token(params: $params) { is_valid claims } }', + 'query validateJWTToken($params: ValidateJWTTokenRequest!){validate_jwt_token(params: $params) { is_valid claims } }', variables: { params, }, @@ -498,11 +492,11 @@ export class Authorizer { }; validateSession = async ( - params?: Types.ValidateSessionInput, - ): Promise> => { + params?: Types.ValidateSessionRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ - query: `query validateSession($params: ValidateSessionInput){validate_session(params: $params) { is_valid user { ${userFragment} } } }`, + query: `query validateSession($params: ValidateSessionRequest){validate_session(params: $params) { is_valid user { ${userFragment} } } }`, variables: { params, }, @@ -517,12 +511,12 @@ export class Authorizer { }; verifyEmail = async ( - data: Types.VerifyEmailInput, - ): Promise> => { + data: Types.VerifyEmailRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` - mutation verifyEmail($data: VerifyEmailInput!) { verify_email(params: $data) { ${authTokenFragment}}} + mutation verifyEmail($data: VerifyEmailRequest!) { verify_email(params: $data) { ${authTokenFragment}}} `, variables: { data }, }); @@ -536,27 +530,27 @@ export class Authorizer { }; resendVerifyEmail = async ( - data: ResendVerifyEmailInput, - ): Promise> => { + data: Types.ResendVerifyEmailRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` - mutation resendVerifyEmail($data: ResendVerifyEmailInput!) { resend_verify_email(params: $data) { message }} + mutation resendVerifyEmail($data: ResendVerifyEmailRequest!) { resend_verify_email(params: $data) { message }} `, variables: { data }, }); return res?.errors?.length ? this.errorResponse(res.errors) - : this.okResponse(res.data?.verify_email); + : this.okResponse(res.data?.resend_verify_email); } catch (err) { return this.errorResponse([err]); } }; verifyOtp = async ( - data: Types.VerifyOtpInput, - ): Promise> => { + data: Types.VerifyOtpRequest, + ): Promise> => { try { const res = await this.graphqlQuery({ query: ` @@ -574,10 +568,10 @@ export class Authorizer { }; // helper to execute graphql queries - // takes in any query or mutation string as input + // takes in any query or mutation string as value graphqlQuery = async ( - data: Types.GraphqlQueryInput, - ): Promise => { + data: Types.GraphqlQueryRequest, + ): Promise => { const fetcher = getFetcher(); const res = await fetcher(`${this.config.authorizerURL}/graphql`, { method: 'POST', @@ -601,14 +595,14 @@ export class Authorizer { return { data: json.data, errors: [] }; }; - errorResponse = (errors: Error[]): ApiResponse => { + errorResponse = (errors: Error[]): Types.ApiResponse => { return { data: undefined, errors, }; }; - okResponse = (data: any): ApiResponse => { + okResponse = (data: any): Types.ApiResponse => { return { data, errors: [], diff --git a/src/types.ts b/src/types.ts index 8c8d07e..aa9002a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,188 +13,313 @@ export interface ConfigType { extraHeaders?: Record; } +// Pagination +export interface Pagination { + limit: number; + page: number; + offset: number; + total: number; +} + +// Meta +export interface Meta { + version: string; + client_id: string; + is_google_login_enabled: boolean; + is_facebook_login_enabled: boolean; + is_github_login_enabled: boolean; + is_linkedin_login_enabled: boolean; + is_apple_login_enabled: boolean; + is_discord_login_enabled: boolean; + is_twitter_login_enabled: boolean; + is_microsoft_login_enabled: boolean; + is_twitch_login_enabled: boolean; + is_roblox_login_enabled: boolean; + is_email_verification_enabled: boolean; + is_basic_authentication_enabled: boolean; + is_magic_link_login_enabled: boolean; + is_sign_up_enabled: boolean; + is_strong_password_enabled: boolean; + is_multi_factor_auth_enabled: boolean; + is_mobile_basic_authentication_enabled: boolean; + is_phone_verification_enabled: boolean; +} + +// User export interface User { id: string; - email: string; - preferred_username: string; + email: string | null; email_verified: boolean; signup_methods: string; - given_name?: string | null; - family_name?: string | null; - middle_name?: string | null; - nickname?: string | null; - picture?: string | null; - gender?: string | null; - birthdate?: string | null; - phone_number?: string | null; - phone_number_verified?: boolean | null; - roles?: string[]; - created_at: number; - updated_at: number; - is_multi_factor_auth_enabled?: boolean; - app_data?: Record; + given_name: string | null; + family_name: string | null; + middle_name: string | null; + nickname: string | null; + preferred_username: string | null; + gender: string | null; + birthdate: string | null; + phone_number: string | null; + phone_number_verified: boolean; + picture: string | null; + roles: string[]; + created_at: number | null; + updated_at: number | null; + revoked_timestamp: number | null; + is_multi_factor_auth_enabled: boolean | null; + app_data: Record | null; } -export interface AuthToken { - message?: string; - access_token: string; - expires_in: number; - id_token: string; - refresh_token?: string; - user?: User; - should_show_email_otp_screen?: boolean; - should_show_mobile_otp_screen?: boolean; - should_show_totp_screen?: boolean; - authenticator_scanner_image?: string; - authenticator_secret?: string; - authenticator_recovery_codes?: string[]; +// Users +export interface Users { + pagination: Pagination; + users: User[]; } -export interface GenericResponse { +// VerificationRequest +export interface VerificationRequest { + id: string; + identifier: string | null; + token: string | null; + email: string | null; + expires: number | null; + created_at: number | null; + updated_at: number | null; + nonce: string | null; + redirect_uri: string | null; +} + +// VerificationRequests +export interface VerificationRequests { + pagination: Pagination; + verification_requests: VerificationRequest[]; +} + +// AuthorizerError (GraphQL Error type - renamed to avoid conflict with native Error) +export interface AuthorizerError { message: string; + reason: string; } -export type Headers = Record; +// AuthResponse +export interface AuthResponse { + message: string; + should_show_email_otp_screen: boolean | null; + should_show_mobile_otp_screen: boolean | null; + should_show_totp_screen: boolean | null; + access_token: string | null; + id_token: string | null; + refresh_token: string | null; + expires_in: number | null; + user: User | null; + authenticator_scanner_image: string | null; + authenticator_secret: string | null; + authenticator_recovery_codes: string[] | null; +} + +// Keep AuthToken as alias for backward compatibility +export type AuthToken = AuthResponse; -export interface LoginInput { - email?: string; - phone_number?: string; +// Response +export interface Response { + message: string; +} + +// Keep GenericResponse as alias for backward compatibility +export type GenericResponse = Response; + +// ForgotPasswordResponse +export interface ForgotPasswordResponse { + message: string; + should_show_mobile_otp_screen: boolean | null; +} + +// InviteMembersResponse +export interface InviteMembersResponse { + message: string; + Users: User[]; +} + +// LoginRequest +export interface LoginRequest { + email?: string | null; + phone_number?: string | null; password: string; - roles?: string[]; - scope?: string[]; - state?: string; + roles?: string[] | null; + scope?: string[] | null; + state?: string | null; } -export interface SignupInput { - email?: string; +// SignUpRequest +export interface SignUpRequest { + email?: string | null; + given_name?: string | null; + family_name?: string | null; + middle_name?: string | null; + nickname?: string | null; + gender?: string | null; + birthdate?: string | null; + phone_number?: string | null; + picture?: string | null; password: string; confirm_password: string; - given_name?: string; - family_name?: string; - middle_name?: string; - nickname?: string; - picture?: string; - gender?: string; - birthdate?: string; - phone_number?: string; - roles?: string[]; - scope?: string[]; - redirect_uri?: string; - is_multi_factor_auth_enabled?: boolean; - state?: string; - app_data?: Record; + roles?: string[] | null; + scope?: string[] | null; + redirect_uri?: string | null; + is_multi_factor_auth_enabled?: boolean | null; + state?: string | null; + app_data?: Record | null; } -export interface MagicLinkLoginInput { +// Keep SignupRequest as alias for backward compatibility +export type SignupRequest = SignUpRequest; + +// MagicLinkLoginRequest +export interface MagicLinkLoginRequest { email: string; - roles?: string[]; - scopes?: string[]; - state?: string; - redirect_uri?: string; + roles?: string[] | null; + scope?: string[] | null; + state?: string | null; + redirect_uri?: string | null; } -export interface VerifyEmailInput { +// VerifyEmailRequest +export interface VerifyEmailRequest { token: string; - state?: string; + state?: string | null; } -export interface ResendVerifyEmailInput { +// ResendVerifyEmailRequest +export interface ResendVerifyEmailRequest { email: string; identifier: string; + state?: string | null; } -export interface VerifyOtpInput { - email?: string; - phone_number?: string; +// VerifyOTPRequest +export interface VerifyOTPRequest { + email?: string | null; + phone_number?: string | null; otp: string; - state?: string; - is_totp?: boolean; + is_totp?: boolean | null; + state?: string | null; } -export interface ResendOtpInput { - email?: string; - phone_number?: string; -} +// Keep VerifyOtpRequest as alias for backward compatibility +export type VerifyOtpRequest = VerifyOTPRequest; -export interface GraphqlQueryInput { - query: string; - variables?: Record; - headers?: Headers; +// ResendOTPRequest +export interface ResendOTPRequest { + email?: string | null; + phone_number?: string | null; + state?: string | null; } -export interface MetaData { - version: string; - client_id: string; - is_google_login_enabled: boolean; - is_facebook_login_enabled: boolean; - is_github_login_enabled: boolean; - is_linkedin_login_enabled: boolean; - is_apple_login_enabled: boolean; - is_twitter_login_enabled: boolean; - is_microsoft_login_enabled: boolean; - is_twitch_login_enabled: boolean; - is_roblox_login_enabled: boolean; - is_email_verification_enabled: boolean; - is_basic_authentication_enabled: boolean; - is_magic_link_login_enabled: boolean; - is_sign_up_enabled: boolean; - is_strong_password_enabled: boolean; - is_multi_factor_auth_enabled: boolean; - is_mobile_basic_authentication_enabled: boolean; - is_phone_verification_enabled: boolean; -} +// Keep ResendOtpRequest as alias for backward compatibility +export type ResendOtpRequest = ResendOTPRequest; -export interface UpdateProfileInput { - old_password?: string; - new_password?: string; - confirm_new_password?: string; - email?: string; - given_name?: string; - family_name?: string; - middle_name?: string; - nickname?: string; - gender?: string; - birthdate?: string; - phone_number?: string; - picture?: string; - is_multi_factor_auth_enabled?: boolean; - app_data?: Record; +// UpdateProfileRequest +export interface UpdateProfileRequest { + old_password?: string | null; + new_password?: string | null; + confirm_new_password?: string | null; + email?: string | null; + given_name?: string | null; + family_name?: string | null; + middle_name?: string | null; + nickname?: string | null; + gender?: string | null; + birthdate?: string | null; + phone_number?: string | null; + picture?: string | null; + is_multi_factor_auth_enabled?: boolean | null; + app_data?: Record | null; } -export interface ForgotPasswordInput { - email?: string; - phone_number?: string; - state?: string; - redirect_uri?: string; +// UpdateUserRequest (admin only) +export interface UpdateUserRequest { + id: string; + email?: string | null; + email_verified?: boolean | null; + given_name?: string | null; + family_name?: string | null; + middle_name?: string | null; + nickname?: string | null; + gender?: string | null; + birthdate?: string | null; + phone_number?: string | null; + phone_number_verified?: boolean | null; + picture?: string | null; + roles?: string[] | null; + is_multi_factor_auth_enabled?: boolean | null; + app_data?: Record | null; } -export interface ForgotPasswordResponse { - message: string; - should_show_mobile_otp_screen?: boolean; +// ForgotPasswordRequest +export interface ForgotPasswordRequest { + email?: string | null; + phone_number?: string | null; + state?: string | null; + redirect_uri?: string | null; } -export interface ResetPasswordInput { - token?: string; - otp?: string; - phone_number?: string; +// ResetPasswordRequest +export interface ResetPasswordRequest { + token?: string | null; + otp?: string | null; + phone_number?: string | null; password: string; confirm_password: string; } -export interface SessionQueryInput { - roles?: string[]; +// Keep ResetPasswordInput as alias for backward compatibility +export type ResetPasswordInput = ResetPasswordRequest; + +// DeleteUserRequest (admin only) +export interface DeleteUserRequest { + email: string; } -export interface IsValidJWTQueryInput { - jwt: string; - roles?: string[]; +// SessionQueryRequest +export interface SessionQueryRequest { + roles?: string[] | null; + scope?: string[] | null; } -export interface ValidJWTResponse { - valid: string; - message: string; +// Keep SessionQueryInput as alias for backward compatibility +export type SessionQueryInput = SessionQueryRequest; + +// ValidateJWTTokenRequest +export interface ValidateJWTTokenRequest { + token_type: string; + token: string; + roles?: string[] | null; +} + +// Keep ValidateJWTTokenInput as alias for backward compatibility +export type ValidateJWTTokenInput = ValidateJWTTokenRequest; + +// ValidateJWTTokenResponse +export interface ValidateJWTTokenResponse { + is_valid: boolean; + claims: Record; } +// ValidateSessionRequest +export interface ValidateSessionRequest { + cookie: string; + roles?: string[] | null; +} + +// Keep ValidateSessionInput as alias for backward compatibility +export type ValidateSessionInput = ValidateSessionRequest; + +// ValidateSessionResponse +export interface ValidateSessionResponse { + is_valid: boolean; + user: User; +} + +// OAuth types (not part of GraphQL schema, but used for OAuth flow) export enum OAuthProviders { Apple = 'apple', Github = 'github', @@ -205,6 +330,7 @@ export enum OAuthProviders { Microsoft = 'microsoft', Twitch = 'twitch', Roblox = 'roblox', + Discord = 'discord', } export enum ResponseTypes { @@ -212,12 +338,15 @@ export enum ResponseTypes { Token = 'token', } -export interface AuthorizeInput { +export interface AuthorizeRequest { response_type: ResponseTypes; use_refresh_token?: boolean; response_mode?: string; } +// Keep AuthorizeInput as alias for backward compatibility +export type AuthorizeInput = AuthorizeRequest; + export interface AuthorizeResponse { state: string; code?: string; @@ -229,12 +358,15 @@ export interface RevokeTokenInput { refresh_token: string; } -export interface GetTokenInput { +export interface GetTokenRequest { code?: string; grant_type?: string; refresh_token?: string; } +// Keep GetTokenInput as alias for backward compatibility +export type GetTokenInput = GetTokenRequest; + export interface GetTokenResponse { access_token: string; expires_in: number; @@ -242,23 +374,28 @@ export interface GetTokenResponse { refresh_token?: string; } -export interface ValidateJWTTokenInput { - token_type: 'access_token' | 'id_token' | 'refresh_token'; - token: string; - roles?: string[]; -} +// GraphQL query request +export type Headers = Record; -export interface ValidateJWTTokenResponse { - is_valid: boolean; - claims: Record; +export interface GraphqlQueryRequest { + query: string; + variables?: Record; + headers?: Headers; } -export interface ValidateSessionInput { - cookie?: string; +// Deprecated types (for backward compatibility) +export interface IsValidJWTQueryInput { + jwt: string; roles?: string[]; } -export interface ValidateSessionResponse { - is_valid: boolean; - user: User; +export interface ValidJWTResponse { + valid: string; + message: string; } + +// Keep MetaDataResponse as alias for backward compatibility +export type MetaDataResponse = Meta; + +// Keep MetaData as alias for backward compatibility +export type MetaData = Meta; diff --git a/tsup.config.ts b/tsup.config.ts index 1dc47ad..f8ca8de 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from 'tsup'; import pkg from './package.json'; + const external = [...Object.keys(pkg.dependencies || {})]; export default defineConfig(() => [ + // Node.js builds (ESM + CJS) { entryPoints: ['src/index.ts'], outDir: 'lib', @@ -10,11 +12,13 @@ export default defineConfig(() => [ format: ['esm', 'cjs'], clean: true, dts: true, - minify: true, + minify: false, // Don't minify Node.js builds for better debugging + sourcemap: true, external, }, + // Browser IIFE build { - entry: { bundle: 'src/index.ts' }, + entryPoints: ['src/index.ts'], outDir: 'lib', format: ['iife'], globalName: 'authorizerdev', @@ -22,11 +26,10 @@ export default defineConfig(() => [ minify: true, platform: 'browser', dts: false, - name: 'authorizer', - // esbuild `globalName` option generates `var authorizerdev = (() => {})()` - // and var is not guaranteed to assign to the global `window` object so we make sure to assign it + // esbuild's globalName creates `var authorizerdev = ...` which works in browsers + // but we ensure it's also on window for compatibility footer: { - js: 'window.__TAURI__ = authorizerdev', + js: 'if (typeof window !== "undefined") { window.authorizerdev = authorizerdev; }', }, outExtension() { return { From bc2fe40e363129949c9ce5d4240fa3f2b3b03abc Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 30 Dec 2025 20:17:19 +0530 Subject: [PATCH 2/5] chore: add rc release cmd --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index acd0678..a9ce78d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@authorizerdev/authorizer-js", - "version": "2.0.3", + "version": "3.0.0-rc.1", "packageManager": "pnpm@7.28.0", "author": "Lakhan Samani", "license": "MIT", @@ -37,6 +37,7 @@ "prepublishOnly": "pnpm run build", "release": "pnpm build && bumpp --commit --push --tag && pnpm publish", "release-beta": "pnpm build && bumpp --commit --push --tag && pnpm publish --tag beta", + "release-rc": "pnpm build && pnpm publish --tag rc", "lint": "eslint --ignore-pattern 'tsup.config.ts' --ext .ts,.tsx,.js,.jsx,.json .", "lint:fix": "eslint --ignore-pattern 'tsup.config.ts' --ext .ts,.tsx,.js,.jsx,.json . --fix" }, From f23d4a8271b3b78eb597c142ecd67fda31410272 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 30 Dec 2025 20:22:09 +0530 Subject: [PATCH 3/5] chore: fix .npmrc --- .npmrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.npmrc b/.npmrc index e2a389c..e69de29 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +0,0 @@ -ignore-workspace-root-check=true \ No newline at end of file From 30248dd576a933d5f27271766e862e0150d4b26b Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 2 Mar 2026 01:25:58 +0530 Subject: [PATCH 4/5] chore: fix tests --- __test__/index.test.ts | 131 +++++++++++++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/__test__/index.test.ts b/__test__/index.test.ts index 5f91fd1..1083ffd 100644 --- a/__test__/index.test.ts +++ b/__test__/index.test.ts @@ -1,10 +1,15 @@ +import { randomUUID } from 'node:crypto'; import { GenericContainer, StartedTestContainer, Wait } from 'testcontainers'; import { ApiResponse, AuthToken, Authorizer } from '../lib'; -const authorizerConfig = { +const authorizerConfig: { + authorizerURL: string; + redirectURL: string; + adminSecret: string; + clientID?: string; +} = { authorizerURL: 'http://localhost:8080', redirectURL: 'http://localhost:8080/app', - // clientID: '3fab5e58-5693-46f2-8123-83db8133cd22', adminSecret: 'secret', }; @@ -18,22 +23,54 @@ const testConfig = { maginLinkLoginEmail: 'test_magic_link@test.com', }; -// Using etheral.email for email sink: https://ethereal.email/create -const authorizerENV = { - ENV: 'production', - DATABASE_URL: 'data.db', - DATABASE_TYPE: 'sqlite', - CUSTOM_ACCESS_TOKEN_SCRIPT: - 'function(user,tokenPayload){var data = tokenPayload;data.extra = {\'x-extra-id\': user.id};return data;}', - DISABLE_PLAYGROUND: 'true', - SMTP_HOST: 'smtp.ethereal.email', - SMTP_PASSWORD: 'WncNxwVFqb6nBjKDQJ', - SMTP_USERNAME: 'sydnee.lesch77@ethereal.email', - LOG_LELVEL: 'debug', - SMTP_PORT: '587', - SENDER_EMAIL: 'test@authorizer.dev', - ADMIN_SECRET: 'secret', -}; +// Build v2 CLI args for authorizer (see authorizer/cmd/root.go). Using etheral.email for email sink. +function buildAuthorizerCliArgs(): { args: string[]; clientId: string } { + const clientId = randomUUID(); + const clientSecret = randomUUID(); + const jwtSecret = randomUUID(); + const customAccessTokenScript = + 'function(user,tokenPayload){var data = tokenPayload;data.extra = {\'x-extra-id\': user.id};return data;}'; + + const args = [ + '--client-id', + clientId, + '--client-secret', + clientSecret, + '--jwt-type', + 'HS256', + '--jwt-secret', + jwtSecret, + '--admin-secret', + authorizerConfig.adminSecret, + '--env', + 'production', + '--database-type', + 'sqlite', + '--database-url', + '/tmp/authorizer.db', + '--custom-access-token-script', + customAccessTokenScript, + '--enable-playground', + 'false', + '--log-level', + 'debug', + '--smtp-host', + 'smtp.ethereal.email', + '--smtp-port', + '587', + '--smtp-username', + 'sydnee.lesch77@ethereal.email', + '--smtp-password', + 'WncNxwVFqb6nBjKDQJ', + '--smtp-sender-email', + 'test@authorizer.dev', + '--enable-email-verification', + 'true', + '--enable-magic-link-login', + 'true', + ]; + return { args, clientId }; +} // const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -46,8 +83,10 @@ describe('Integration Tests - authorizer-js', () => { let authorizer: Authorizer; beforeAll(async () => { - container = await new GenericContainer('lakhansamani/authorizer:latest') - .withEnvironment(authorizerENV) + const { args, clientId } = buildAuthorizerCliArgs(); + + container = await new GenericContainer('lakhansamani/authorizer:2.0.0-rc.6') + .withCommand(args) .withExposedPorts(8080) .withWaitStrategy(Wait.forHttp('/health', 8080).forStatusCode(200)) .start(); @@ -58,15 +97,12 @@ describe('Integration Tests - authorizer-js', () => { authorizerConfig.redirectURL = `http://${container.getHost()}:${container.getMappedPort( 8080, )}/app`; + authorizerConfig.clientID = clientId; console.log('Authorizer URL:', authorizerConfig.authorizerURL); authorizer = new Authorizer(authorizerConfig); - // get metadata const metadataRes = await authorizer.getMetaData(); - // await sleep(50000); expect(metadataRes?.data).toBeDefined(); - if (metadataRes?.data?.client_id) { - authorizer.config.clientID = metadataRes?.data?.client_id; - } + expect(metadataRes?.data?.client_id).toBe(clientId); }); afterAll(async () => { @@ -101,10 +137,13 @@ describe('Integration Tests - authorizer-js', () => { (i: { email: string }) => i.email === testConfig.email, ); expect(item).not.toBeNull(); + expect(item?.token).toBeDefined(); const verifyEmailRes = await authorizer.verifyEmail({ token: item.token }); expect(verifyEmailRes?.data).toBeDefined(); expect(verifyEmailRes?.errors).toHaveLength(0); + expect(verifyEmailRes?.data?.access_token).toBeDefined(); + expect(verifyEmailRes?.data?.access_token).not.toBeNull(); expect(verifyEmailRes?.data?.access_token?.length).toBeGreaterThan(0); }); @@ -117,13 +156,23 @@ describe('Integration Tests - authorizer-js', () => { }); expect(loginRes?.data).toBeDefined(); expect(loginRes?.errors).toHaveLength(0); - expect(loginRes?.data?.access_token.length).not.toEqual(0); - expect(loginRes?.data?.refresh_token?.length).not.toEqual(0); - expect(loginRes?.data?.expires_in).not.toEqual(0); - expect(loginRes?.data?.id_token.length).not.toEqual(0); + expect(loginRes?.data?.access_token).toBeDefined(); + expect(loginRes?.data?.access_token).not.toBeNull(); + expect(loginRes?.data?.access_token?.length).toBeGreaterThan(0); + expect(loginRes?.data?.refresh_token).toBeDefined(); + expect(loginRes?.data?.refresh_token).not.toBeNull(); + expect(loginRes?.data?.refresh_token?.length).toBeGreaterThan(0); + expect(loginRes?.data?.expires_in).toBeDefined(); + expect(loginRes?.data?.expires_in).not.toBeNull(); + expect(loginRes?.data?.expires_in).toBeGreaterThan(0); + expect(loginRes?.data?.id_token).toBeDefined(); + expect(loginRes?.data?.id_token).not.toBeNull(); + expect(loginRes?.data?.id_token?.length).toBeGreaterThan(0); }); it('should validate jwt token', async () => { + expect(loginRes?.data?.access_token).toBeDefined(); + expect(loginRes?.data?.access_token).not.toBeNull(); const validateRes = await authorizer.validateJWTToken({ token_type: 'access_token', token: loginRes?.data?.access_token || '', @@ -134,6 +183,8 @@ describe('Integration Tests - authorizer-js', () => { }); it('should update profile successfully', async () => { + expect(loginRes?.data?.access_token).toBeDefined(); + expect(loginRes?.data?.access_token).not.toBeNull(); const updateProfileRes = await authorizer.updateProfile( { given_name: 'bob', @@ -147,28 +198,36 @@ describe('Integration Tests - authorizer-js', () => { }); it('should fetch profile successfully', async () => { + expect(loginRes?.data?.access_token).toBeDefined(); + expect(loginRes?.data?.access_token).not.toBeNull(); const profileRes = await authorizer.getProfile({ Authorization: `Bearer ${loginRes?.data?.access_token}`, }); expect(profileRes?.data).toBeDefined(); expect(profileRes?.errors).toHaveLength(0); + expect(profileRes?.data?.given_name).toBeDefined(); expect(profileRes?.data?.given_name).toMatch('bob'); }); it('should get access_token using refresh_token', async () => { + expect(loginRes?.data?.refresh_token).toBeDefined(); + expect(loginRes?.data?.refresh_token).not.toBeNull(); const tokenRes = await authorizer.getToken({ grant_type: 'refresh_token', - refresh_token: loginRes?.data?.refresh_token, + refresh_token: loginRes?.data?.refresh_token || '', }); expect(tokenRes?.data).toBeDefined(); expect(tokenRes?.errors).toHaveLength(0); - expect(tokenRes?.data?.access_token.length).not.toEqual(0); + expect(tokenRes?.data?.access_token).toBeDefined(); + expect(tokenRes?.data?.access_token?.length).toBeGreaterThan(0); if (loginRes && loginRes.data) { loginRes.data.access_token = tokenRes?.data?.access_token || ''; } }); it('should deactivate account', async () => { + expect(loginRes?.data?.access_token).toBeDefined(); + expect(loginRes?.data?.access_token).not.toBeNull(); const deactivateRes = await authorizer.deactivateAccount({ Authorization: `Bearer ${loginRes?.data?.access_token}`, }); @@ -177,11 +236,13 @@ describe('Integration Tests - authorizer-js', () => { }); it('should throw error while accessing profile after deactivation', async () => { + expect(loginRes?.data?.access_token).toBeDefined(); const resp = await authorizer.getProfile({ Authorization: `Bearer ${loginRes?.data?.access_token}`, }); expect(resp?.data).toBeUndefined(); - expect(resp?.errors).toHaveLength(1); + expect(resp?.errors).toBeDefined(); + expect(resp?.errors.length).toBeGreaterThan(0); }); describe('magic link login', () => { @@ -195,6 +256,7 @@ describe('Integration Tests - authorizer-js', () => { it('should verify email', async () => { const verificationRequestsRes = await authorizer.graphqlQuery({ query: verificationRequests, + variables: {}, headers: { 'x-authorizer-admin-secret': authorizerConfig.adminSecret, }, @@ -202,15 +264,20 @@ describe('Integration Tests - authorizer-js', () => { const requests = verificationRequestsRes?.data?._verification_requests .verification_requests; + expect(verificationRequestsRes?.data).toBeDefined(); + expect(verificationRequestsRes?.errors).toHaveLength(0); const item = requests.find( (i: { email: string }) => i.email === testConfig.maginLinkLoginEmail, ); expect(item).not.toBeNull(); + expect(item?.token).toBeDefined(); const verifyEmailRes = await authorizer.verifyEmail({ token: item.token, }); expect(verifyEmailRes?.data).toBeDefined(); expect(verifyEmailRes?.errors).toHaveLength(0); + expect(verifyEmailRes?.data?.user).toBeDefined(); + expect(verifyEmailRes?.data?.user?.signup_methods).toBeDefined(); expect(verifyEmailRes?.data?.user?.signup_methods).toContain( 'magic_link_login', ); From 4f48ad107863ab817a2a4f1375a1bab8c9bfeef5 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 3 Mar 2026 18:02:04 +0530 Subject: [PATCH 5/5] chore: update build packager --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a9ce78d..7ecd6d2 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "scripts": { "start": "tsup --watch", "build": "tsup", - "test": "pnpm run build && jest --testTimeout=500000 --runInBand", + "test": "npm run build && jest --testTimeout=500000 --runInBand", "prepare": "husky install", - "prepublishOnly": "pnpm run build", + "prepublishOnly": "npm run build", "release": "pnpm build && bumpp --commit --push --tag && pnpm publish", "release-beta": "pnpm build && bumpp --commit --push --tag && pnpm publish --tag beta", "release-rc": "pnpm build && pnpm publish --tag rc",