@opengovsg/myinfo-gov-client
Advanced tools
Comparing version 2.1.3 to 3.0.0
export * from './MyInfoGovClient.class'; | ||
export * from './myinfo-data'; | ||
export * from './myinfo-types'; | ||
export * from './errors'; |
@@ -14,3 +14,3 @@ "use strict"; | ||
__exportStar(require("./MyInfoGovClient.class"), exports); | ||
__exportStar(require("./myinfo-data"), exports); | ||
__exportStar(require("./myinfo-types"), exports); | ||
__exportStar(require("./errors"), exports); |
@@ -7,62 +7,261 @@ export declare enum MyInfoSource { | ||
} | ||
export declare enum MyInfoSex { | ||
Male = "M", | ||
Female = "F", | ||
Unknown = "U" | ||
} | ||
interface IMyInfoMetadata { | ||
lastUpdated: string; | ||
declare type Metadata = { | ||
lastupdated: string; | ||
source: MyInfoSource; | ||
classification: 'C'; | ||
} | ||
export interface IMyInfoBasicField extends IMyInfoMetadata { | ||
value?: string; | ||
} | ||
export interface IMyInfoHighLowField extends IMyInfoMetadata { | ||
high: number; | ||
low: number; | ||
} | ||
export interface IMyInfoFieldWithDesc extends IMyInfoBasicField { | ||
}; | ||
declare type UnavailableField = Metadata & { | ||
unavailable: true; | ||
}; | ||
declare type PossiblyAvailableMetadata = Metadata & { | ||
unavailable?: false; | ||
}; | ||
declare type MyInfoField<CustomFields> = UnavailableField | (CustomFields & PossiblyAvailableMetadata); | ||
declare type StringValue = { | ||
value: string; | ||
}; | ||
declare type NumberValue = { | ||
value: number; | ||
}; | ||
declare type BooleanValue = { | ||
value: boolean; | ||
}; | ||
export declare type BasicField = MyInfoField<StringValue>; | ||
declare type CodeAndDesc = { | ||
code: string; | ||
desc: string; | ||
}; | ||
export declare type FieldWithCodeAndDesc = MyInfoField<CodeAndDesc>; | ||
export declare enum AddressType { | ||
Singapore = "SG", | ||
Unformatted = "Unformatted" | ||
} | ||
export interface IMyInfoSexField extends IMyInfoMetadata { | ||
value: MyInfoSex; | ||
} | ||
export interface IChildrenBirthRecord { | ||
birthcertno: IMyInfoBasicField; | ||
name: IMyInfoBasicField; | ||
hanyupinyinname: IMyInfoBasicField; | ||
aliasname: IMyInfoBasicField; | ||
hanyupinyinaliasname: IMyInfoBasicField; | ||
marriedname: IMyInfoBasicField; | ||
sex: IMyInfoSexField; | ||
race: IMyInfoBasicField; | ||
secondaryrace: IMyInfoBasicField; | ||
dob: IMyInfoBasicField; | ||
tob: IMyInfoBasicField; | ||
dialect: IMyInfoBasicField; | ||
lifestatus: IMyInfoBasicField; | ||
} | ||
export interface IMyInfoPhoneNo extends IMyInfoMetadata { | ||
code: string; | ||
prefix: string; | ||
nbr: string; | ||
} | ||
export interface IMyInfoAddr extends IMyInfoMetadata { | ||
country: string; | ||
unit: string; | ||
street: string; | ||
block: string; | ||
postal: string; | ||
floor: string; | ||
building: string; | ||
} | ||
declare type SGAddress = { | ||
type: AddressType.Singapore; | ||
block: StringValue; | ||
building: StringValue; | ||
floor: StringValue; | ||
unit: StringValue; | ||
street: StringValue; | ||
postal: StringValue; | ||
country: CodeAndDesc; | ||
}; | ||
declare type UnformattedAddress = { | ||
type: AddressType.Unformatted; | ||
line1: StringValue; | ||
line2: StringValue; | ||
}; | ||
export declare type MyInfoAddress = MyInfoField<SGAddress> | MyInfoField<UnformattedAddress>; | ||
declare type HDBOwnershipCustomFields = { | ||
noofowners: NumberValue; | ||
address: SGAddress | UnformattedAddress; | ||
hdbtype: CodeAndDesc; | ||
leasecommencementdate: StringValue; | ||
termoflease: NumberValue; | ||
dateofpurchase: StringValue; | ||
dateofownershiptransfer: StringValue; | ||
loangranted: NumberValue; | ||
originalloanrepayment: NumberValue; | ||
balanceloanrepayment: { | ||
years: NumberValue; | ||
months: NumberValue; | ||
}; | ||
outstandingloanbalance: NumberValue; | ||
monthlyloaninstalment: NumberValue; | ||
}; | ||
export declare type HDBOwnership = MyInfoField<HDBOwnershipCustomFields>; | ||
export declare type OwnerPrivate = MyInfoField<BooleanValue>; | ||
declare type MyInfoPhoneNumberCustomFields = { | ||
prefix: StringValue; | ||
areacode: StringValue; | ||
nbr: StringValue; | ||
}; | ||
export declare type MyInfoPhoneNumber = MyInfoField<MyInfoPhoneNumberCustomFields>; | ||
declare type ChildCustomFields = { | ||
birthcertno: StringValue; | ||
name: StringValue; | ||
hanyupinyinname: StringValue; | ||
aliasname: StringValue; | ||
hanyupinyinaliasname: StringValue; | ||
marriedname: StringValue; | ||
sex: CodeAndDesc; | ||
race: CodeAndDesc; | ||
secondaryrace: CodeAndDesc; | ||
dialect: CodeAndDesc; | ||
lifestatus: CodeAndDesc; | ||
dob: StringValue; | ||
tob: StringValue; | ||
}; | ||
export declare type ChildBelow21 = MyInfoField<ChildCustomFields>; | ||
export declare type ChildAbove21 = MyInfoField<Pick<ChildCustomFields, 'birthcertno'>>; | ||
export declare type ChildRecord = ChildBelow21 | ChildAbove21; | ||
declare type SponsoredChildCustomFields = { | ||
nric: StringValue; | ||
name: StringValue; | ||
hanyupinyinname: StringValue; | ||
aliasname: StringValue; | ||
hanyupinyinaliasname: StringValue; | ||
marriedname: StringValue; | ||
sex: CodeAndDesc; | ||
race: CodeAndDesc; | ||
secondaryrace: CodeAndDesc; | ||
dialect: CodeAndDesc; | ||
dob: StringValue; | ||
birthcountry: CodeAndDesc; | ||
lifestatus: CodeAndDesc; | ||
residentialstatus: CodeAndDesc; | ||
nationality: CodeAndDesc; | ||
scprgrantdate: StringValue; | ||
}; | ||
export declare type SponsoredChildBelow21 = MyInfoField<SponsoredChildCustomFields>; | ||
export declare type SponsoredChildAbove21 = MyInfoField<Pick<SponsoredChildCustomFields, 'nric'>>; | ||
export declare type SponsoredChildRecord = SponsoredChildBelow21 | SponsoredChildAbove21; | ||
export declare type MyInfoOccupation = MyInfoField<StringValue> | MyInfoField<CodeAndDesc>; | ||
export declare type HouseholdIncome = MyInfoField<{ | ||
high: NumberValue; | ||
low: NumberValue; | ||
}>; | ||
declare type MyInfoVehicleCustomFields = { | ||
vehicleno: StringValue; | ||
type: StringValue; | ||
iulabelno: StringValue; | ||
make: StringValue; | ||
model: StringValue; | ||
chassisno: StringValue; | ||
engineno: StringValue; | ||
motorno: StringValue; | ||
yearofmanufacture: StringValue; | ||
firstregistrationdate: StringValue; | ||
originalregistrationdate: StringValue; | ||
coecategory: StringValue; | ||
coeexpirydate: StringValue; | ||
roadtaxexpirydate: StringValue; | ||
quotapremium: NumberValue; | ||
openmarketvalue: NumberValue; | ||
co2emission: NumberValue; | ||
status: CodeAndDesc; | ||
primarycolour: StringValue; | ||
secondarycolour: StringValue; | ||
attachment1: StringValue; | ||
attachment2: StringValue; | ||
attachment3: StringValue; | ||
scheme: StringValue; | ||
thcemission: NumberValue; | ||
coemission: NumberValue; | ||
noxemission: NumberValue; | ||
pmemission: NumberValue; | ||
enginecapacity: NumberValue; | ||
powerrate: NumberValue; | ||
effectiveownership: StringValue; | ||
propellant: StringValue; | ||
maximumunladenweight: NumberValue; | ||
maximumladenweight: NumberValue; | ||
minimumparfbenefit: NumberValue; | ||
nooftransfers: NumberValue; | ||
vpc: StringValue; | ||
}; | ||
export declare type MyInfoVehicle = MyInfoField<MyInfoVehicleCustomFields>; | ||
declare type StartEndDate = { | ||
startdate: StringValue; | ||
enddate: StringValue; | ||
}; | ||
declare type PDL = { | ||
validity: CodeAndDesc; | ||
expirydate: StringValue; | ||
classes: { | ||
class: StringValue; | ||
}[]; | ||
}; | ||
declare type QDL = { | ||
validity: CodeAndDesc; | ||
expirydate: StringValue; | ||
classes: { | ||
class: StringValue; | ||
issuedate: StringValue; | ||
}[]; | ||
}; | ||
declare type DrivingLicenceCustomFields = { | ||
comstatus: CodeAndDesc; | ||
totaldemeritpoints: NumberValue; | ||
suspension: StartEndDate; | ||
disqualification: StartEndDate; | ||
revocation: StartEndDate; | ||
pdl: PDL; | ||
qdl: QDL; | ||
photocardserialno: StringValue; | ||
}; | ||
export declare type DrivingLicence = MyInfoField<DrivingLicenceCustomFields>; | ||
export declare type MerdekaGen = MyInfoField<{ | ||
eligibility: BooleanValue; | ||
quantum: NumberValue; | ||
message: CodeAndDesc; | ||
}>; | ||
export declare type SilverSupport = MyInfoField<{ | ||
eligibility: BooleanValue; | ||
amount: StringValue; | ||
year: StringValue; | ||
}>; | ||
export declare type GSTVoucher = MyInfoField<{ | ||
exclusion: BooleanValue; | ||
signup: BooleanValue; | ||
gstmedisave: NumberValue; | ||
gstregular: NumberValue; | ||
gstspecial: NumberValue; | ||
year: StringValue; | ||
}>; | ||
declare type NOABasicFields = { | ||
amount: NumberValue; | ||
yearofassessment: StringValue; | ||
}; | ||
export declare type NOABasic = MyInfoField<NOABasicFields>; | ||
declare type NOAFullFields = { | ||
amount: NumberValue; | ||
yearofassessment: StringValue; | ||
employment: NumberValue; | ||
trade: NumberValue; | ||
rent: NumberValue; | ||
interest: NumberValue; | ||
taxclearance: StringValue; | ||
category: StringValue; | ||
}; | ||
export declare type NOAFull = MyInfoField<NOAFullFields>; | ||
export declare type NOAHistoryBasic = MyInfoField<{ | ||
noas: NOABasicFields[]; | ||
}>; | ||
export declare type NOAHistoryFull = MyInfoField<{ | ||
noas: NOAFullFields[]; | ||
}>; | ||
export declare type CPFContributions = MyInfoField<{ | ||
history: { | ||
employer: StringValue; | ||
date: StringValue; | ||
month: StringValue; | ||
amount: NumberValue; | ||
}[]; | ||
}>; | ||
export declare type CPFEmployers = MyInfoField<{ | ||
history: { | ||
employer: StringValue; | ||
month: StringValue; | ||
}[]; | ||
}>; | ||
export declare type CPFBalances = MyInfoField<{ | ||
ma: NumberValue; | ||
oa: NumberValue; | ||
sa: NumberValue; | ||
ra?: NumberValue; | ||
}>; | ||
/** | ||
* Keys of data returned by Person API. | ||
*/ | ||
export declare enum MyInfoAttribute { | ||
UinFin = "uinfin", | ||
Name = "name", | ||
MarriedName = "marriedname", | ||
HanYuPinYinName = "hanyupinyinname", | ||
AliasName = "aliasname", | ||
HanYuPinYinAliasName = "hanyupinyinaliasname", | ||
MarriedName = "marriedname", | ||
Sex = "sex", | ||
Race = "race", | ||
SecondaryRace = "secondaryrace", | ||
Dialect = "dialect", | ||
@@ -72,16 +271,13 @@ Nationality = "nationality", | ||
BirthCountry = "birthcountry", | ||
SecondaryRace = "secondaryrace", | ||
ResidentialStatus = "residentialstatus", | ||
PassportNumber = "passportnumber", | ||
PassportExpiryDate = "passportexpirydate", | ||
Email = "email", | ||
MobileNo = "mobileno", | ||
HomeNo = "homeno", | ||
RegisteredAddress = "regadd", | ||
HousingType = "housingtype", | ||
HDBType = "hdbtype", | ||
MailAddress = "mailadd", | ||
BillingAddress = "billadd", | ||
HDBOwnership = "hdbownership", | ||
OwnerPrivate = "ownerprivate", | ||
Email = "email", | ||
MobileNo = "mobileno", | ||
MaritalStatus = "marital", | ||
EducationLevel = "edulevel", | ||
MarriageCertNumber = "marriagecertno", | ||
@@ -92,54 +288,87 @@ CountryOfMarriage = "countryofmarriage", | ||
ChildrenBirthRecords = "childrenbirthrecords", | ||
Relationships = "relationships", | ||
GradYear = "gradyear", | ||
SchoolName = "schoolname", | ||
SponsoredChildrenRecords = "sponsoredchildrenrecords", | ||
Occupation = "occupation", | ||
Employment = "employment", | ||
WorkPassStatus = "workpassstatus", | ||
WorkPassExpiryDate = "workpassexpirydate", | ||
PassType = "passtype", | ||
PassStatus = "passstatus", | ||
PassExpiryDate = "passexpirydate", | ||
EmploymentSector = "employmentsector", | ||
HouseholdIncome = "householdincome", | ||
VehicleNumber = "vehno" | ||
Vehicles = "vehicles", | ||
DrivingLicence = "drivinglicence", | ||
MerdekaGen = "merdekagen", | ||
SilverSupport = "silversupport", | ||
GSTVoucher = "gstvoucher", | ||
NOABasic = "noa-basic", | ||
NOA = "noa", | ||
NOAHistoryBasic = "noahistory-basic", | ||
NOAHistory = "noahistory", | ||
CPFContributions = "cpfcontributions", | ||
CPFEmployers = "cpfemployers", | ||
CPFBalances = "cpfbalances" | ||
} | ||
export interface IPersonBasic { | ||
name: IMyInfoBasicField; | ||
hanyupinyinname: IMyInfoBasicField; | ||
aliasname: IMyInfoBasicField; | ||
hanyupinyinaliasname: IMyInfoBasicField; | ||
marriedname: IMyInfoBasicField; | ||
sex: IMyInfoSexField; | ||
race: IMyInfoBasicField; | ||
secondaryrace: IMyInfoBasicField; | ||
dialect: IMyInfoBasicField; | ||
nationality: IMyInfoBasicField; | ||
dob: IMyInfoBasicField; | ||
birthcountry: IMyInfoBasicField; | ||
residentialstatus: IMyInfoBasicField; | ||
passportnumber: IMyInfoBasicField; | ||
passportexpirydate: IMyInfoBasicField; | ||
regadd: IMyInfoAddr; | ||
mailadd: IMyInfoAddr; | ||
billadd: IMyInfoAddr; | ||
housingtype: IMyInfoBasicField; | ||
hdbtype: IMyInfoBasicField; | ||
email: IMyInfoBasicField; | ||
homeno: IMyInfoPhoneNo; | ||
mobileno: IMyInfoPhoneNo; | ||
marital: IMyInfoBasicField; | ||
marriagecertno: IMyInfoBasicField; | ||
countryofmarriage: IMyInfoBasicField; | ||
marriagedate: IMyInfoBasicField; | ||
divorcedate: IMyInfoBasicField; | ||
childrenbirthrecords: IChildrenBirthRecord[]; | ||
relationships: Record<string, unknown>[]; | ||
edulevel: IMyInfoBasicField; | ||
gradyear: IMyInfoBasicField; | ||
schoolname: IMyInfoFieldWithDesc; | ||
occupation: IMyInfoFieldWithDesc; | ||
employment: IMyInfoBasicField; | ||
workpassstatus: IMyInfoBasicField; | ||
workpassexpirydate: IMyInfoBasicField; | ||
householdincome: IMyInfoHighLowField; | ||
vehno: IMyInfoBasicField; | ||
uinFin: string; | ||
} | ||
declare type IPersonFull = { | ||
uinfin: BasicField; | ||
name: BasicField; | ||
hanyupinyinname: BasicField; | ||
aliasname: BasicField; | ||
hanyupinyinaliasname: BasicField; | ||
marriedname: BasicField; | ||
sex: FieldWithCodeAndDesc; | ||
race: FieldWithCodeAndDesc; | ||
secondaryrace: FieldWithCodeAndDesc; | ||
dialect: FieldWithCodeAndDesc; | ||
nationality: FieldWithCodeAndDesc; | ||
dob: BasicField; | ||
birthcountry: FieldWithCodeAndDesc; | ||
residentialstatus: FieldWithCodeAndDesc; | ||
passportnumber: BasicField; | ||
passportexpirydate: BasicField; | ||
regadd: MyInfoAddress; | ||
housingtype: FieldWithCodeAndDesc; | ||
hdbtype: FieldWithCodeAndDesc; | ||
hdbownership: HDBOwnership[]; | ||
ownerprivate: OwnerPrivate; | ||
email: BasicField; | ||
mobileno: MyInfoPhoneNumber; | ||
marital: FieldWithCodeAndDesc; | ||
marriagecertno: BasicField; | ||
countryofmarriage: FieldWithCodeAndDesc; | ||
marriagedate: BasicField; | ||
divorcedate: BasicField; | ||
childrenbirthrecords: ChildRecord[]; | ||
sponsoredchildrenrecords: SponsoredChildRecord[]; | ||
occupation: MyInfoOccupation; | ||
employment: BasicField; | ||
passtype: FieldWithCodeAndDesc; | ||
passstatus: BasicField; | ||
passexpirydate: BasicField; | ||
employmentsector: BasicField; | ||
householdincome: HouseholdIncome; | ||
vehicles: MyInfoVehicle[]; | ||
drivinglicence: DrivingLicence; | ||
merdekagen: MerdekaGen; | ||
silversupport: SilverSupport; | ||
gstvoucher: GSTVoucher; | ||
'noa-basic': NOABasic; | ||
noa: NOAFull; | ||
'noahistory-basic': NOAHistoryBasic; | ||
noahistory: NOAHistoryFull; | ||
cpfcontributions: CPFContributions; | ||
cpfemployers: CPFEmployers; | ||
cpfbalances: CPFBalances; | ||
}; | ||
/** | ||
* Shape of data returned by the Person API. | ||
*/ | ||
export declare type IPerson = Partial<IPersonFull>; | ||
export declare type HDBOwnershipScope = `${MyInfoAttribute.HDBOwnership}.${keyof HDBOwnershipCustomFields}`; | ||
export declare type ChildrenBirthRecordsScope = `${MyInfoAttribute.ChildrenBirthRecords}.${keyof ChildCustomFields}`; | ||
export declare type SponsoredChildrenRecordsScope = `${MyInfoAttribute.SponsoredChildrenRecords}.${keyof SponsoredChildCustomFields}`; | ||
export declare type VehiclesScope = `${MyInfoAttribute.Vehicles}.${keyof MyInfoVehicleCustomFields}`; | ||
export declare type DrivingLicenceScope = `${MyInfoAttribute.DrivingLicence}.${Exclude<keyof DrivingLicenceCustomFields, 'suspension' | 'disqualification' | 'revocation' | 'pdl' | 'qdl'>}` | `${MyInfoAttribute.DrivingLicence}.${'suspension' | 'disqualification' | 'revocation'}.${keyof StartEndDate}` | `${MyInfoAttribute.DrivingLicence}.pdl.${keyof PDL}` | `${MyInfoAttribute.DrivingLicence}.qdl.${keyof QDL}`; | ||
/** | ||
* Valid scopes (requested attributes) to get from MyInfo. | ||
*/ | ||
export declare type MyInfoScope = Exclude<keyof IPerson, 'hdbownership' | 'childrenbirthrecords' | 'sponsoredchildrenrecords' | 'vehicles' | 'drivinglicence'> | HDBOwnershipScope | ChildrenBirthRecordsScope | SponsoredChildrenRecordsScope | VehiclesScope | DrivingLicenceScope; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MyInfoAttribute = exports.MyInfoSex = exports.MyInfoSource = void 0; | ||
exports.MyInfoAttribute = exports.AddressType = exports.MyInfoSource = void 0; | ||
var MyInfoSource; | ||
@@ -11,17 +11,21 @@ (function (MyInfoSource) { | ||
})(MyInfoSource = exports.MyInfoSource || (exports.MyInfoSource = {})); | ||
var MyInfoSex; | ||
(function (MyInfoSex) { | ||
MyInfoSex["Male"] = "M"; | ||
MyInfoSex["Female"] = "F"; | ||
MyInfoSex["Unknown"] = "U"; | ||
})(MyInfoSex = exports.MyInfoSex || (exports.MyInfoSex = {})); | ||
var AddressType; | ||
(function (AddressType) { | ||
AddressType["Singapore"] = "SG"; | ||
AddressType["Unformatted"] = "Unformatted"; | ||
})(AddressType = exports.AddressType || (exports.AddressType = {})); | ||
/** | ||
* Keys of data returned by Person API. | ||
*/ | ||
var MyInfoAttribute; | ||
(function (MyInfoAttribute) { | ||
MyInfoAttribute["UinFin"] = "uinfin"; | ||
MyInfoAttribute["Name"] = "name"; | ||
MyInfoAttribute["MarriedName"] = "marriedname"; | ||
MyInfoAttribute["HanYuPinYinName"] = "hanyupinyinname"; | ||
MyInfoAttribute["AliasName"] = "aliasname"; | ||
MyInfoAttribute["HanYuPinYinAliasName"] = "hanyupinyinaliasname"; | ||
MyInfoAttribute["MarriedName"] = "marriedname"; | ||
MyInfoAttribute["Sex"] = "sex"; | ||
MyInfoAttribute["Race"] = "race"; | ||
MyInfoAttribute["SecondaryRace"] = "secondaryrace"; | ||
MyInfoAttribute["Dialect"] = "dialect"; | ||
@@ -31,16 +35,13 @@ MyInfoAttribute["Nationality"] = "nationality"; | ||
MyInfoAttribute["BirthCountry"] = "birthcountry"; | ||
MyInfoAttribute["SecondaryRace"] = "secondaryrace"; | ||
MyInfoAttribute["ResidentialStatus"] = "residentialstatus"; | ||
MyInfoAttribute["PassportNumber"] = "passportnumber"; | ||
MyInfoAttribute["PassportExpiryDate"] = "passportexpirydate"; | ||
MyInfoAttribute["Email"] = "email"; | ||
MyInfoAttribute["MobileNo"] = "mobileno"; | ||
MyInfoAttribute["HomeNo"] = "homeno"; | ||
MyInfoAttribute["RegisteredAddress"] = "regadd"; | ||
MyInfoAttribute["HousingType"] = "housingtype"; | ||
MyInfoAttribute["HDBType"] = "hdbtype"; | ||
MyInfoAttribute["MailAddress"] = "mailadd"; | ||
MyInfoAttribute["BillingAddress"] = "billadd"; | ||
MyInfoAttribute["HDBOwnership"] = "hdbownership"; | ||
MyInfoAttribute["OwnerPrivate"] = "ownerprivate"; | ||
MyInfoAttribute["Email"] = "email"; | ||
MyInfoAttribute["MobileNo"] = "mobileno"; | ||
MyInfoAttribute["MaritalStatus"] = "marital"; | ||
MyInfoAttribute["EducationLevel"] = "edulevel"; | ||
MyInfoAttribute["MarriageCertNumber"] = "marriagecertno"; | ||
@@ -51,11 +52,22 @@ MyInfoAttribute["CountryOfMarriage"] = "countryofmarriage"; | ||
MyInfoAttribute["ChildrenBirthRecords"] = "childrenbirthrecords"; | ||
MyInfoAttribute["Relationships"] = "relationships"; | ||
MyInfoAttribute["GradYear"] = "gradyear"; | ||
MyInfoAttribute["SchoolName"] = "schoolname"; | ||
MyInfoAttribute["SponsoredChildrenRecords"] = "sponsoredchildrenrecords"; | ||
MyInfoAttribute["Occupation"] = "occupation"; | ||
MyInfoAttribute["Employment"] = "employment"; | ||
MyInfoAttribute["WorkPassStatus"] = "workpassstatus"; | ||
MyInfoAttribute["WorkPassExpiryDate"] = "workpassexpirydate"; | ||
MyInfoAttribute["PassType"] = "passtype"; | ||
MyInfoAttribute["PassStatus"] = "passstatus"; | ||
MyInfoAttribute["PassExpiryDate"] = "passexpirydate"; | ||
MyInfoAttribute["EmploymentSector"] = "employmentsector"; | ||
MyInfoAttribute["HouseholdIncome"] = "householdincome"; | ||
MyInfoAttribute["VehicleNumber"] = "vehno"; | ||
MyInfoAttribute["Vehicles"] = "vehicles"; | ||
MyInfoAttribute["DrivingLicence"] = "drivinglicence"; | ||
MyInfoAttribute["MerdekaGen"] = "merdekagen"; | ||
MyInfoAttribute["SilverSupport"] = "silversupport"; | ||
MyInfoAttribute["GSTVoucher"] = "gstvoucher"; | ||
MyInfoAttribute["NOABasic"] = "noa-basic"; | ||
MyInfoAttribute["NOA"] = "noa"; | ||
MyInfoAttribute["NOAHistoryBasic"] = "noahistory-basic"; | ||
MyInfoAttribute["NOAHistory"] = "noahistory"; | ||
MyInfoAttribute["CPFContributions"] = "cpfcontributions"; | ||
MyInfoAttribute["CPFEmployers"] = "cpfemployers"; | ||
MyInfoAttribute["CPFBalances"] = "cpfbalances"; | ||
})(MyInfoAttribute = exports.MyInfoAttribute || (exports.MyInfoAttribute = {})); |
/// <reference types="node" /> | ||
import { IPersonBasic } from './myinfo-types'; | ||
export declare enum Mode { | ||
import { IPerson, MyInfoScope } from './myinfo-types'; | ||
/** | ||
* Mode in which to initialise the client, which determines the | ||
* MyInfo endpoint to call. | ||
*/ | ||
export declare enum MyInfoMode { | ||
Dev = "dev", | ||
@@ -8,389 +12,143 @@ Staging = "stg", | ||
} | ||
export interface IConfig { | ||
realm: string; | ||
appId: string; | ||
/** | ||
* Parameters for MyInfoGovClient constructor. | ||
*/ | ||
export interface IMyInfoConfig { | ||
clientId: string; | ||
clientSecret: string | Buffer; | ||
singpassEserviceId: string; | ||
privateKey: Buffer | string; | ||
clientId?: string; | ||
mode?: Mode; | ||
redirectEndpoint: string; | ||
clientPrivateKey: string | Buffer; | ||
myInfoPublicKey: string | Buffer; | ||
mode?: MyInfoMode; | ||
} | ||
export interface IPersonBasicRequest { | ||
uinFin: string; | ||
requestedAttributes?: string[]; | ||
txnNo?: string; | ||
/** | ||
* Parameters to create a redirect URL to initialise MyInfo login. | ||
*/ | ||
export interface IAuthRequest { | ||
purpose: string; | ||
requestedAttributes: MyInfoScope[]; | ||
relayState: string; | ||
singpassEserviceId?: string; | ||
redirectEndpoint?: string; | ||
} | ||
interface IBaseStringSpec { | ||
httpMethod: string; | ||
url: string; | ||
appId: string; | ||
clientId: string; | ||
singpassEserviceId: string; | ||
nonce: string; | ||
requestedAttributes: string[]; | ||
timestamp: number; | ||
txnNo?: string; | ||
/** | ||
* Response shape of getPerson. | ||
*/ | ||
export interface IPersonResponse { | ||
uinFin: string; | ||
data: IPerson; | ||
} | ||
declare type MyInfoParsedResponse = Omit<IPersonBasic, 'uinFin'>; | ||
/** | ||
* Convenience wrapper around the MyInfo API for Government | ||
* digital services. | ||
*/ | ||
export declare class MyInfoGovClient { | ||
realm: string; | ||
appId: string; | ||
clientId: string; | ||
clientSecret: string; | ||
redirectEndpoint: string; | ||
clientPrivateKey: string; | ||
myInfoPublicKey: string; | ||
singpassEserviceId: string; | ||
mode: Mode; | ||
privateKey: string; | ||
baseUrl: string; | ||
mode: MyInfoMode; | ||
baseAPIUrl: string; | ||
/** | ||
* Constructor for MyInfoGovClient, which helps call the internal, | ||
* non-public-facing MyInfo TUO. | ||
* @param config Config object to create an MyInfoGovClient. | ||
* @param config.realm - Name of MyInfo application e.g. 'FormSG' | ||
* @param config.appId - ID of MyInfo application | ||
* e.g. 'STG2-GOVTECH-FORMSG-SP' or 'PROD2-GOVTECH-FORMSG-SP' | ||
* @param config.singpassEserviceId - ID registered with SingPass | ||
* e.g. 'GOVTECH-FORMSG-SP' | ||
* @param config.privateKey` - RSA-SHA256 private key, which must | ||
* correspond with public key provided to MyInfo during onboarding process. | ||
* @param [config.clientId] - ID of MyInfo client. Defaults to `appId` | ||
* if not provided. | ||
* @param [config.mode] - dev/stg/prod, which sets up the correct | ||
* endpoint to call. Defaults to prod if not provided. | ||
* Class constructor. Each instance of MyInfoGovClient uses one set | ||
* of credentials registered with MyInfo. | ||
* @param config Configuration object | ||
* @param config.clientId Client ID (also known as App ID) | ||
* @param config.clientSecret Client secret provided by MyInfo | ||
* @param config.singpassEserviceId The default e-service ID registered | ||
* with SingPass to use. Can be overridden if necessary in | ||
* `createRedirectURL` and `getPerson` functions. | ||
* @param config.redirectEndpoint Endpoint to which user should be redirected | ||
* after login | ||
* @param config.clientPrivateKey RSA-SHA256 private key, | ||
* which must correspond with public key provided to MyInfo during the | ||
* onboarding process | ||
* @param config.myInfoPublicKey MyInfo server's public key for verifying | ||
* their signature | ||
* @param config.mode Optional mode, which determines the MyInfo endpoint | ||
* to call. Defaults to production mode. | ||
* @throws {MissingParamsError} Throws if any required parameter is missing | ||
* | ||
*/ | ||
constructor(config: IConfig); | ||
constructor(config: IMyInfoConfig); | ||
/** | ||
* Make a GET request to Person-Basic endpoint. | ||
* @param {IPersonBasicRequest} personRequest - A request for attributes of a person to | ||
* MyInfo | ||
* @param personRequest.uinFin - NRIC number | ||
* @param personRequest.requestedAttributes Array of | ||
* attributes of person to request from MyInfo. Will query all fields if | ||
* not provided, or if it is an empty list. | ||
* @param personRequest.txnNo - Optional transaction number | ||
* @param singpassEserviceId - Optional Singpass eService ID that if provided, overrides the default eService ID in the constructor. | ||
* If provided, the API will be called with this Singpass eService ID | ||
* instead of the one provided to the constructor during object instantiation. | ||
* @return - Promise resolving to a person object containing requested fields | ||
* @example | ||
* myInfo.getPersonBasic({uinFin, requestedAttributes, txnNo}) | ||
* .then(function(personObject) { | ||
* console.log(personObject) | ||
* }) | ||
* | ||
* { | ||
* "name": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "TAN XIAO HUI" | ||
* }, | ||
* "hanyupinyinname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "CHEN XIAO HUI" | ||
* }, | ||
* "aliasname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "TRICIA TAN XIAO HUI" | ||
* }, | ||
* "hanyupinyinaliasname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "marriedname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "sex": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "F" | ||
* }, | ||
* "race": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "CN" | ||
* }, | ||
* "secondaryrace": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "EU" | ||
* }, | ||
* "dialect": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "nationality": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "dob": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "1958-05-17" | ||
* }, | ||
* "birthcountry": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "residentialstatus": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "C" | ||
* }, | ||
* "passportnumber": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "E35463874W" | ||
* }, | ||
* "passportexpirydate": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "2020-01-01" | ||
* }, | ||
* "regadd": { | ||
* "country": "SG", | ||
* "unit": "128", | ||
* "street": "BEDOK NORTH AVENUE 1", | ||
* "lastupdated": "2016-03-11", | ||
* "block": "548", | ||
* "source": "1", | ||
* "postal": "460548", | ||
* "classification": "C", | ||
* "floor": "09", | ||
* "building": "" | ||
* }, | ||
* "mailadd": { | ||
* "country": "SG", | ||
* "unit": "128", | ||
* "street": "BEDOK NORTH AVENUE 1", | ||
* "lastupdated": "2016-03-11", | ||
* "block": "548", | ||
* "source": "2", | ||
* "postal": "460548", | ||
* "classification": "C", | ||
* "floor": "09", | ||
* "building": "" | ||
* }, | ||
* "billadd": { | ||
* "country": "SG", | ||
* "unit": "", | ||
* "street": "", | ||
* "lastupdated": "", | ||
* "block": "", | ||
* "source": "", | ||
* "postal": "", | ||
* "classification": "", | ||
* "floor": "", | ||
* "building": "" | ||
* }, | ||
* "housingtype": { | ||
* "lastupdated": null, | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "hdbtype": { | ||
* "lastupdated": "2015-12-23", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "111" | ||
* }, | ||
* "email": { | ||
* "lastupdated": "2017-12-13", | ||
* "source": "4", | ||
* "classification": "C", | ||
* "value": "test@gmail.com" | ||
* }, | ||
* "homeno": { | ||
* "code": "65", | ||
* "prefix": "+", | ||
* "lastupdated": "2017-11-20", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "nbr": "66132665" | ||
* }, | ||
* "mobileno": { | ||
* "code": "65", | ||
* "prefix": "+", | ||
* "lastupdated": "2017-12-13", | ||
* "source": "4", | ||
* "classification": "C", | ||
* "nbr": "97324992" | ||
* }, | ||
* "marital": { | ||
* "lastupdated": "2017-03-29", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "1" | ||
* }, | ||
* "marriagecertno": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "123456789012345" | ||
* }, | ||
* "countryofmarriage": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "marriagedate": { | ||
* "lastupdated": "", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "divorcedate": { | ||
* "lastupdated": "", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "childrenbirthrecords": [ | ||
* {}, | ||
* {}, | ||
* {} | ||
* ], | ||
* "relationships": [ | ||
* {}, | ||
* {} | ||
* ], | ||
* "edulevel": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "3" | ||
* }, | ||
* "gradyear": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "1978" | ||
* }, | ||
* "schoolname": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "T07GS3011J", | ||
* "desc": "SIGLAP SECONDARY SCHOOL" | ||
* }, | ||
* "occupation": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "53201", | ||
* "desc": "HEALTHCARE ASSISTANT" | ||
* }, | ||
* "employment": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "ALPHA" | ||
* }, | ||
* "workpassstatus": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "Live" | ||
* }, | ||
* "workpassexpirydate": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "2018-12-31" | ||
* }, | ||
* "householdincome": { | ||
* "high": "5999", | ||
* "low": "5000", | ||
* "lastupdated": "2017-10-24", | ||
* "source": "2", | ||
* "classification": "C" | ||
* }, | ||
* "vehno": { | ||
* "lastupdated": "", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "" | ||
* } | ||
* } | ||
* Constructs a redirect URL which the user can visit to initialise | ||
* SingPass login and consent to providing the given MyInfo attributes. | ||
* @param config Configuration object | ||
* @param config.purpose Purpose of requesting the data, which will be | ||
* shown to user | ||
* @param config.requestedAttributes MyInfo attributes which the user must | ||
* consent to provide | ||
* @param config.relayState State to be forwarded to the redirect endpoint | ||
* via query parameters | ||
* @param config.singpassEserviceId Optional alternative e-service ID. | ||
* Defaults to the e-serviceId provided in the constructor. | ||
* @param config.redirectEndpoint Optional alternative redirect endpoint. | ||
* Defaults to the endpoint provided in the constructor. | ||
* @returns The URL which the user should visit to log in to SingPass | ||
* and consent to providing the given attributes. | ||
*/ | ||
getPersonBasic({ uinFin, requestedAttributes, txnNo, singpassEserviceId: seId, }: IPersonBasicRequest): Promise<IPersonBasic>; | ||
createRedirectURL({ purpose, requestedAttributes, relayState, singpassEserviceId, redirectEndpoint, }: IAuthRequest): string; | ||
/** | ||
* Decrypts a JWE response string | ||
* @param jweResponse Fullstop-delimited jweResponse string | ||
* @return Promise which resolves to a parsed response | ||
* Retrieves the given MyInfo attributes from the Person endpoint after | ||
* the client has logged in to SingPass and consented to providing the given | ||
* attributes. | ||
* @param accessToken Access token given by MyInfo | ||
* @param requestedAttributes Attributes to request from Myinfo. Should correspond | ||
* to the attributes provided when initiating SingPass login. | ||
* @param singpassEserviceId Optional alternative e-service ID. | ||
* Defaults to the e-serviceId provided in the constructor. | ||
* @returns Object containing the user's NRIC/FIN and the data | ||
* @throws {InvalidTokenSignatureError} Throws if the JWT signature is invalid | ||
* @throws {WrongAccessTokenShapeError} Throws if decoded JWT has an unexpected | ||
* type or shape | ||
* @throws {MyInfoResponseError} Throws if MyInfo returns a non-200 response | ||
* @throws {DecryptDataError} Throws if an error occurs while decrypting data | ||
* @throws {InvalidDataSignatureError} Throws if signature on data is invalid | ||
* @throws {WrongDataShapeError} Throws if decrypted data from MyInfo is | ||
* of the wrong type | ||
*/ | ||
_decryptJwe(jweResponse: string): Promise<MyInfoParsedResponse>; | ||
getPerson(accessToken: string, requestedAttributes: MyInfoScope[], singpassEserviceId?: string): Promise<IPersonResponse>; | ||
/** | ||
* Internal function to generate the APEX signature basestring. The | ||
* resultant basestring must be signed with the private key and attached | ||
* to the request header when calling MyInfo. | ||
* @param basestrConfig Object containing all the fields | ||
* necessary for generating basestring | ||
* @param basestrConfig.httpMethod One of GET/POST/PUT/DELETE | ||
* @param basestrConfig.url Full url endpoint, such as | ||
* https://myinfosgstg.api.gov.sg/gov/test/v2/person-basic/ | ||
* @param basestrConfig.appId MyInfo App ID | ||
* @param basestrConfig.clientId MyInfo Client ID | ||
* @param basestrConfig.singpassEserviceId Client SingPass | ||
* e-Service ID | ||
* @param basestrConfig.nonce A randomly generated base64 | ||
* number | ||
* @param basestrConfig.requestedAttributes List of person | ||
* attributes being requested. | ||
* @param basestrConfig.timestamp Timestamp of request using | ||
* Date.now(). | ||
* @param basestrConfig.txnNo Optional transaction number. | ||
* | ||
* @return The basestring to be signed by _signBaseString(). | ||
* Extracts the UIN or FIN from the access token. | ||
* @param accessToken JSON web token, which is the access token provided | ||
* by the Token endpoint | ||
* @returns The UIN or FIN decoded from the JWT | ||
* @throws {InvalidTokenSignatureError} Throws if the JWT signature is invalid | ||
* @throws {WrongAccessTokenShapeError} Throws if decoded JWT has an unexpected | ||
* type or shape | ||
*/ | ||
_formulateBaseString({ httpMethod, url, appId, clientId, singpassEserviceId, nonce, requestedAttributes, timestamp, txnNo, }: IBaseStringSpec): string; | ||
extractUinFin(accessToken: string): string; | ||
/** | ||
* Returns signature of basestring signed with private key | ||
* @param basestring - Basestring | ||
* @param privateKey - Private key | ||
* @param outputFormat - One of latin1/hex/base64, passed to | ||
* crypto.sign.sign() | ||
* @return Signature of basestring signed with privateKey. | ||
* Retrieves the access token from the Token endpoint. | ||
* @param authCode Authorisation code provided to the redirect endpoint | ||
* @returns The access token as a JWT | ||
* @throws {MyInfoResponseError} Throws if MyInfo returns a non-200 response | ||
* @throws {MissingAccessTokenError} Throws if MyInfo response does not | ||
* contain the access token | ||
*/ | ||
_signBaseString(basestring: string, privateKey: string, outputFormat: 'latin1' | 'hex' | 'base64'): string; | ||
getAccessToken(authCode: string): Promise<string>; | ||
/** | ||
* Create the APEX authentication header from constituent parts. | ||
* @param realm Realm | ||
* @param appId Client appId | ||
* @param nonce A randomly generated base64 number | ||
* @param signature Signature generated with _signBaseString | ||
* @param timestamp Current timestamp generated with Date.now() | ||
* @return Authentication header to be included in API | ||
* call | ||
* Generates the content of the 'Authorization' header to be sent | ||
* with a request to MyInfo. | ||
* @param method HTTP method to be used for the request | ||
* @param url Endpoint to which the request is being sent | ||
* @param urlParams Query parameters being sent with the request | ||
* @returns The content which should be provided as the Authorization | ||
* header | ||
*/ | ||
_formulateAuthHeader({ realm, appId, nonce, signature, timestamp }: { | ||
realm: string; | ||
appId: string; | ||
nonce: string; | ||
signature: string; | ||
timestamp: number; | ||
}): string; | ||
_generateAuthHeader(method: 'POST' | 'GET', url: string, urlParams: Record<string, string>): string; | ||
/** | ||
* Decrypts a JWE response string. | ||
* @param jwe Fullstop-delimited JWE | ||
* @returns The decrypted data, with signature already verified | ||
* @throws {DecryptDataError} Throws if an error occurs while decrypting data | ||
* @throws {InvalidDataSignatureError} Throws if signature on data is invalid | ||
* @throws {WrongDataShapeError} Throws if decrypted data from MyInfo is | ||
* of the wrong type | ||
*/ | ||
_decryptJWE(jwe: string): Promise<IPerson>; | ||
} | ||
export {}; |
@@ -13,2 +13,38 @@ "use strict"; | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -19,517 +55,323 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MyInfoGovClient = exports.Mode = void 0; | ||
exports.MyInfoGovClient = exports.MyInfoMode = void 0; | ||
var qs_1 = __importDefault(require("qs")); | ||
var crypto_1 = __importDefault(require("crypto")); | ||
var axios_1 = __importDefault(require("axios")); | ||
var node_jose_1 = __importDefault(require("node-jose")); | ||
var path_1 = __importDefault(require("path")); | ||
var axios_1 = __importDefault(require("axios")); | ||
var qs_1 = __importDefault(require("qs")); | ||
var myinfo_types_1 = require("./myinfo-types"); | ||
var Mode; | ||
(function (Mode) { | ||
Mode["Dev"] = "dev"; | ||
Mode["Staging"] = "stg"; | ||
Mode["Production"] = "prod"; | ||
})(Mode = exports.Mode || (exports.Mode = {})); | ||
var jsonwebtoken_1 = require("jsonwebtoken"); | ||
var util_1 = require("./util"); | ||
var errors_1 = require("./errors"); | ||
/** | ||
* Mode in which to initialise the client, which determines the | ||
* MyInfo endpoint to call. | ||
*/ | ||
var MyInfoMode; | ||
(function (MyInfoMode) { | ||
MyInfoMode["Dev"] = "dev"; | ||
MyInfoMode["Staging"] = "stg"; | ||
MyInfoMode["Production"] = "prod"; | ||
})(MyInfoMode = exports.MyInfoMode || (exports.MyInfoMode = {})); | ||
var BASE_URL = (_a = {}, | ||
_a[Mode.Dev] = 'https://myinfosgstg.api.gov.sg/gov/dev/v1/', | ||
_a[Mode.Staging] = 'https://myinfosgstg.api.gov.sg/gov/test/v2/', | ||
_a[Mode.Production] = 'https://myinfosg.api.gov.sg/gov/v2/', | ||
_a[MyInfoMode.Dev] = 'https://sandbox.api.myinfo.gov.sg/gov/v3', | ||
_a[MyInfoMode.Staging] = 'https://test.api.myinfo.gov.sg/gov/v3', | ||
_a[MyInfoMode.Production] = 'https://api.myinfo.gov.sg/gov/v3', | ||
_a); | ||
var ENDPOINT = { | ||
// Person-Basic API | ||
personBasic: 'person-basic', | ||
// TODO: Create functions to help with Person API | ||
authorise: 'authorise', | ||
token: 'token', | ||
person: 'person', | ||
}; | ||
var ALL_ATTRIBUTES = Object.values(myinfo_types_1.MyInfoAttribute); | ||
var Endpoint; | ||
(function (Endpoint) { | ||
Endpoint["Authorise"] = "/authorise"; | ||
Endpoint["Token"] = "/token"; | ||
Endpoint["Person"] = "/person"; | ||
})(Endpoint || (Endpoint = {})); | ||
/** | ||
* Convenience wrapper around the MyInfo API for Government | ||
* digital services. | ||
*/ | ||
var MyInfoGovClient = /** @class */ (function () { | ||
/** | ||
* Constructor for MyInfoGovClient, which helps call the internal, | ||
* non-public-facing MyInfo TUO. | ||
* @param config Config object to create an MyInfoGovClient. | ||
* @param config.realm - Name of MyInfo application e.g. 'FormSG' | ||
* @param config.appId - ID of MyInfo application | ||
* e.g. 'STG2-GOVTECH-FORMSG-SP' or 'PROD2-GOVTECH-FORMSG-SP' | ||
* @param config.singpassEserviceId - ID registered with SingPass | ||
* e.g. 'GOVTECH-FORMSG-SP' | ||
* @param config.privateKey` - RSA-SHA256 private key, which must | ||
* correspond with public key provided to MyInfo during onboarding process. | ||
* @param [config.clientId] - ID of MyInfo client. Defaults to `appId` | ||
* if not provided. | ||
* @param [config.mode] - dev/stg/prod, which sets up the correct | ||
* endpoint to call. Defaults to prod if not provided. | ||
* Class constructor. Each instance of MyInfoGovClient uses one set | ||
* of credentials registered with MyInfo. | ||
* @param config Configuration object | ||
* @param config.clientId Client ID (also known as App ID) | ||
* @param config.clientSecret Client secret provided by MyInfo | ||
* @param config.singpassEserviceId The default e-service ID registered | ||
* with SingPass to use. Can be overridden if necessary in | ||
* `createRedirectURL` and `getPerson` functions. | ||
* @param config.redirectEndpoint Endpoint to which user should be redirected | ||
* after login | ||
* @param config.clientPrivateKey RSA-SHA256 private key, | ||
* which must correspond with public key provided to MyInfo during the | ||
* onboarding process | ||
* @param config.myInfoPublicKey MyInfo server's public key for verifying | ||
* their signature | ||
* @param config.mode Optional mode, which determines the MyInfo endpoint | ||
* to call. Defaults to production mode. | ||
* @throws {MissingParamsError} Throws if any required parameter is missing | ||
* | ||
*/ | ||
function MyInfoGovClient(config) { | ||
var realm = config.realm, appId = config.appId, clientId = config.clientId, singpassEserviceId = config.singpassEserviceId, mode = config.mode, privateKey = config.privateKey; | ||
if (!realm || !appId || !singpassEserviceId || !privateKey) { | ||
throw new Error('Missing required parameter(s) in constructor:' + | ||
' realm, appId, singpassEserviceId, privateKey'); | ||
var clientId = config.clientId, clientSecret = config.clientSecret, mode = config.mode, singpassEserviceId = config.singpassEserviceId, redirectEndpoint = config.redirectEndpoint, clientPrivateKey = config.clientPrivateKey, myInfoPublicKey = config.myInfoPublicKey; | ||
if (!clientId || | ||
!clientSecret || | ||
!singpassEserviceId || | ||
!redirectEndpoint || | ||
!clientPrivateKey || | ||
!myInfoPublicKey) { | ||
throw new errors_1.MissingParamsError(); | ||
} | ||
this.realm = realm; | ||
this.appId = appId; | ||
this.clientId = clientId || appId; | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret.toString(); | ||
this.redirectEndpoint = redirectEndpoint; | ||
this.mode = mode || MyInfoMode.Production; | ||
this.singpassEserviceId = singpassEserviceId; | ||
this.mode = mode || Mode.Production; | ||
this.privateKey = privateKey.toString().replace(/\n$/, ''); | ||
this.baseUrl = BASE_URL[this.mode] || BASE_URL.prod; | ||
this.clientPrivateKey = clientPrivateKey.toString().replace(/\n$/, ''); | ||
this.myInfoPublicKey = myInfoPublicKey.toString().replace(/\n$/, ''); | ||
this.baseAPIUrl = BASE_URL[this.mode] || BASE_URL.prod; | ||
} | ||
/** | ||
* Make a GET request to Person-Basic endpoint. | ||
* @param {IPersonBasicRequest} personRequest - A request for attributes of a person to | ||
* MyInfo | ||
* @param personRequest.uinFin - NRIC number | ||
* @param personRequest.requestedAttributes Array of | ||
* attributes of person to request from MyInfo. Will query all fields if | ||
* not provided, or if it is an empty list. | ||
* @param personRequest.txnNo - Optional transaction number | ||
* @param singpassEserviceId - Optional Singpass eService ID that if provided, overrides the default eService ID in the constructor. | ||
* If provided, the API will be called with this Singpass eService ID | ||
* instead of the one provided to the constructor during object instantiation. | ||
* @return - Promise resolving to a person object containing requested fields | ||
* @example | ||
* myInfo.getPersonBasic({uinFin, requestedAttributes, txnNo}) | ||
* .then(function(personObject) { | ||
* console.log(personObject) | ||
* }) | ||
* | ||
* { | ||
* "name": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "TAN XIAO HUI" | ||
* }, | ||
* "hanyupinyinname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "CHEN XIAO HUI" | ||
* }, | ||
* "aliasname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "TRICIA TAN XIAO HUI" | ||
* }, | ||
* "hanyupinyinaliasname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "marriedname": { | ||
* "lastupdated": "2015-06-01", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "sex": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "F" | ||
* }, | ||
* "race": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "CN" | ||
* }, | ||
* "secondaryrace": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "EU" | ||
* }, | ||
* "dialect": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "nationality": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "dob": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "1958-05-17" | ||
* }, | ||
* "birthcountry": { | ||
* "lastupdated": "2016-03-11", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "residentialstatus": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "C" | ||
* }, | ||
* "passportnumber": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "E35463874W" | ||
* }, | ||
* "passportexpirydate": { | ||
* "lastupdated": "2017-08-25", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "2020-01-01" | ||
* }, | ||
* "regadd": { | ||
* "country": "SG", | ||
* "unit": "128", | ||
* "street": "BEDOK NORTH AVENUE 1", | ||
* "lastupdated": "2016-03-11", | ||
* "block": "548", | ||
* "source": "1", | ||
* "postal": "460548", | ||
* "classification": "C", | ||
* "floor": "09", | ||
* "building": "" | ||
* }, | ||
* "mailadd": { | ||
* "country": "SG", | ||
* "unit": "128", | ||
* "street": "BEDOK NORTH AVENUE 1", | ||
* "lastupdated": "2016-03-11", | ||
* "block": "548", | ||
* "source": "2", | ||
* "postal": "460548", | ||
* "classification": "C", | ||
* "floor": "09", | ||
* "building": "" | ||
* }, | ||
* "billadd": { | ||
* "country": "SG", | ||
* "unit": "", | ||
* "street": "", | ||
* "lastupdated": "", | ||
* "block": "", | ||
* "source": "", | ||
* "postal": "", | ||
* "classification": "", | ||
* "floor": "", | ||
* "building": "" | ||
* }, | ||
* "housingtype": { | ||
* "lastupdated": null, | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "hdbtype": { | ||
* "lastupdated": "2015-12-23", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "111" | ||
* }, | ||
* "email": { | ||
* "lastupdated": "2017-12-13", | ||
* "source": "4", | ||
* "classification": "C", | ||
* "value": "test@gmail.com" | ||
* }, | ||
* "homeno": { | ||
* "code": "65", | ||
* "prefix": "+", | ||
* "lastupdated": "2017-11-20", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "nbr": "66132665" | ||
* }, | ||
* "mobileno": { | ||
* "code": "65", | ||
* "prefix": "+", | ||
* "lastupdated": "2017-12-13", | ||
* "source": "4", | ||
* "classification": "C", | ||
* "nbr": "97324992" | ||
* }, | ||
* "marital": { | ||
* "lastupdated": "2017-03-29", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "1" | ||
* }, | ||
* "marriagecertno": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "123456789012345" | ||
* }, | ||
* "countryofmarriage": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "SG" | ||
* }, | ||
* "marriagedate": { | ||
* "lastupdated": "", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "divorcedate": { | ||
* "lastupdated": "", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "" | ||
* }, | ||
* "childrenbirthrecords": [ | ||
* {}, | ||
* {}, | ||
* {} | ||
* ], | ||
* "relationships": [ | ||
* {}, | ||
* {} | ||
* ], | ||
* "edulevel": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "3" | ||
* }, | ||
* "gradyear": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "1978" | ||
* }, | ||
* "schoolname": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "T07GS3011J", | ||
* "desc": "SIGLAP SECONDARY SCHOOL" | ||
* }, | ||
* "occupation": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "53201", | ||
* "desc": "HEALTHCARE ASSISTANT" | ||
* }, | ||
* "employment": { | ||
* "lastupdated": "2017-10-11", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "ALPHA" | ||
* }, | ||
* "workpassstatus": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "Live" | ||
* }, | ||
* "workpassexpirydate": { | ||
* "lastupdated": "2018-03-02", | ||
* "source": "1", | ||
* "classification": "C", | ||
* "value": "2018-12-31" | ||
* }, | ||
* "householdincome": { | ||
* "high": "5999", | ||
* "low": "5000", | ||
* "lastupdated": "2017-10-24", | ||
* "source": "2", | ||
* "classification": "C" | ||
* }, | ||
* "vehno": { | ||
* "lastupdated": "", | ||
* "source": "2", | ||
* "classification": "C", | ||
* "value": "" | ||
* } | ||
* } | ||
* Constructs a redirect URL which the user can visit to initialise | ||
* SingPass login and consent to providing the given MyInfo attributes. | ||
* @param config Configuration object | ||
* @param config.purpose Purpose of requesting the data, which will be | ||
* shown to user | ||
* @param config.requestedAttributes MyInfo attributes which the user must | ||
* consent to provide | ||
* @param config.relayState State to be forwarded to the redirect endpoint | ||
* via query parameters | ||
* @param config.singpassEserviceId Optional alternative e-service ID. | ||
* Defaults to the e-serviceId provided in the constructor. | ||
* @param config.redirectEndpoint Optional alternative redirect endpoint. | ||
* Defaults to the endpoint provided in the constructor. | ||
* @returns The URL which the user should visit to log in to SingPass | ||
* and consent to providing the given attributes. | ||
*/ | ||
MyInfoGovClient.prototype.getPersonBasic = function (_a) { | ||
var _this = this; | ||
var uinFin = _a.uinFin, requestedAttributes = _a.requestedAttributes, txnNo = _a.txnNo, seId = _a.singpassEserviceId; | ||
if (!requestedAttributes || requestedAttributes.length === 0) { | ||
requestedAttributes = ALL_ATTRIBUTES; | ||
} | ||
var url = this.baseUrl + path_1.default.join(ENDPOINT.personBasic, uinFin) + '/'; | ||
var nonce = crypto_1.default.randomBytes(32).toString('base64'); | ||
var timestamp = Date.now(); | ||
var singpassEserviceId = seId || this.singpassEserviceId; | ||
// Construct the request basestring | ||
var basestring = this._formulateBaseString({ | ||
httpMethod: 'GET', | ||
url: url, | ||
appId: this.appId, | ||
clientId: this.clientId, | ||
singpassEserviceId: singpassEserviceId, | ||
nonce: nonce, | ||
requestedAttributes: requestedAttributes, | ||
timestamp: timestamp, | ||
txnNo: txnNo, | ||
}); | ||
// Generate the request signature by signing the constructed basestring | ||
// with private key | ||
var signature = this._signBaseString(basestring, this.privateKey, 'base64'); | ||
// Construct the authentication header using the signature, nonce and | ||
// timestamp | ||
var authHeader = this._formulateAuthHeader({ | ||
realm: this.realm, | ||
appId: this.appId, | ||
nonce: nonce, | ||
signature: signature, | ||
timestamp: timestamp, | ||
}); | ||
// Construct the request headers | ||
var headers = { | ||
'Content-Type': 'application/json', | ||
Authorization: authHeader, | ||
Accept: 'application/json', | ||
}; | ||
// Construct the querystring params | ||
var querystring = { | ||
MyInfoGovClient.prototype.createRedirectURL = function (_a) { | ||
var purpose = _a.purpose, requestedAttributes = _a.requestedAttributes, relayState = _a.relayState, singpassEserviceId = _a.singpassEserviceId, redirectEndpoint = _a.redirectEndpoint; | ||
var queryParams = qs_1.default.stringify({ | ||
purpose: purpose, | ||
attributes: requestedAttributes.join(), | ||
state: relayState, | ||
client_id: this.clientId, | ||
singpassEserviceId: singpassEserviceId, | ||
txnNo: txnNo, | ||
}; | ||
return axios_1.default.get(url, { | ||
headers: headers, | ||
params: querystring, | ||
paramsSerializer: function (params) { return qs_1.default.stringify(params); }, | ||
}) | ||
.then(function (response) { | ||
// In dev mode, Axios parses the response for us, otherwise we need to decrypt the JWE | ||
return typeof response.data === 'string' ? _this._decryptJwe(response.data) : Promise.resolve(response.data); | ||
}) | ||
.then(function (personObject) { | ||
var personWithUinFin = __assign(__assign({}, personObject), { uinFin: uinFin }); | ||
return personWithUinFin; | ||
redirect_uri: redirectEndpoint !== null && redirectEndpoint !== void 0 ? redirectEndpoint : this.redirectEndpoint, | ||
sp_esvcId: singpassEserviceId !== null && singpassEserviceId !== void 0 ? singpassEserviceId : this.singpassEserviceId, | ||
}); | ||
return "" + this.baseAPIUrl + Endpoint.Authorise + "?" + queryParams; | ||
}; | ||
/** | ||
* Decrypts a JWE response string | ||
* @param jweResponse Fullstop-delimited jweResponse string | ||
* @return Promise which resolves to a parsed response | ||
* Retrieves the given MyInfo attributes from the Person endpoint after | ||
* the client has logged in to SingPass and consented to providing the given | ||
* attributes. | ||
* @param accessToken Access token given by MyInfo | ||
* @param requestedAttributes Attributes to request from Myinfo. Should correspond | ||
* to the attributes provided when initiating SingPass login. | ||
* @param singpassEserviceId Optional alternative e-service ID. | ||
* Defaults to the e-serviceId provided in the constructor. | ||
* @returns Object containing the user's NRIC/FIN and the data | ||
* @throws {InvalidTokenSignatureError} Throws if the JWT signature is invalid | ||
* @throws {WrongAccessTokenShapeError} Throws if decoded JWT has an unexpected | ||
* type or shape | ||
* @throws {MyInfoResponseError} Throws if MyInfo returns a non-200 response | ||
* @throws {DecryptDataError} Throws if an error occurs while decrypting data | ||
* @throws {InvalidDataSignatureError} Throws if signature on data is invalid | ||
* @throws {WrongDataShapeError} Throws if decrypted data from MyInfo is | ||
* of the wrong type | ||
*/ | ||
MyInfoGovClient.prototype._decryptJwe = function (jweResponse) { | ||
var keystore = node_jose_1.default.JWK.createKeyStore(); | ||
return keystore | ||
.add(this.privateKey, 'pem') | ||
.then(function (jweKey) { | ||
return node_jose_1.default.JWE.createDecrypt(jweKey).decrypt(jweResponse); | ||
}) | ||
.then(function (_a) { | ||
var payload = _a.payload; | ||
return JSON.parse(payload.toString()); | ||
MyInfoGovClient.prototype.getPerson = function (accessToken, requestedAttributes, singpassEserviceId) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var uinFin, url, params, paramsAuthHeader, headers, response, err_1, data; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
uinFin = this.extractUinFin(accessToken); | ||
url = "" + this.baseAPIUrl + Endpoint.Person + "/" + uinFin + "/"; | ||
params = { | ||
client_id: this.clientId, | ||
attributes: requestedAttributes.join(), | ||
sp_esvcId: singpassEserviceId !== null && singpassEserviceId !== void 0 ? singpassEserviceId : this.singpassEserviceId, | ||
}; | ||
paramsAuthHeader = this._generateAuthHeader('GET', url, params); | ||
headers = { | ||
'Cache-Control': 'no-cache', | ||
Authorization: paramsAuthHeader + ",Bearer " + accessToken, | ||
}; | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, axios_1.default.get(url, { | ||
headers: headers, | ||
params: params, | ||
paramsSerializer: qs_1.default.stringify, | ||
})]; | ||
case 2: | ||
response = _a.sent(); | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
err_1 = _a.sent(); | ||
throw new errors_1.MyInfoResponseError(err_1); | ||
case 4: | ||
// In dev mode, the response is automatically parsed to an object by Axios. | ||
if (this.mode === MyInfoMode.Dev) { | ||
if (typeof response.data !== 'object') { | ||
throw new errors_1.WrongDataShapeError(); | ||
} | ||
return [2 /*return*/, { uinFin: uinFin, data: response.data }]; | ||
} | ||
// In non-dev mode, the response is a JWE and must be decrypted | ||
if (typeof response.data !== 'string') { | ||
throw new errors_1.WrongDataShapeError(); | ||
} | ||
return [4 /*yield*/, this._decryptJWE(response.data)]; | ||
case 5: | ||
data = _a.sent(); | ||
return [2 /*return*/, { uinFin: uinFin, data: data }]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Internal function to generate the APEX signature basestring. The | ||
* resultant basestring must be signed with the private key and attached | ||
* to the request header when calling MyInfo. | ||
* @param basestrConfig Object containing all the fields | ||
* necessary for generating basestring | ||
* @param basestrConfig.httpMethod One of GET/POST/PUT/DELETE | ||
* @param basestrConfig.url Full url endpoint, such as | ||
* https://myinfosgstg.api.gov.sg/gov/test/v2/person-basic/ | ||
* @param basestrConfig.appId MyInfo App ID | ||
* @param basestrConfig.clientId MyInfo Client ID | ||
* @param basestrConfig.singpassEserviceId Client SingPass | ||
* e-Service ID | ||
* @param basestrConfig.nonce A randomly generated base64 | ||
* number | ||
* @param basestrConfig.requestedAttributes List of person | ||
* attributes being requested. | ||
* @param basestrConfig.timestamp Timestamp of request using | ||
* Date.now(). | ||
* @param basestrConfig.txnNo Optional transaction number. | ||
* | ||
* @return The basestring to be signed by _signBaseString(). | ||
* Extracts the UIN or FIN from the access token. | ||
* @param accessToken JSON web token, which is the access token provided | ||
* by the Token endpoint | ||
* @returns The UIN or FIN decoded from the JWT | ||
* @throws {InvalidTokenSignatureError} Throws if the JWT signature is invalid | ||
* @throws {WrongAccessTokenShapeError} Throws if decoded JWT has an unexpected | ||
* type or shape | ||
*/ | ||
MyInfoGovClient.prototype._formulateBaseString = function (_a) { | ||
var httpMethod = _a.httpMethod, url = _a.url, appId = _a.appId, clientId = _a.clientId, singpassEserviceId = _a.singpassEserviceId, nonce = _a.nonce, requestedAttributes = _a.requestedAttributes, timestamp = _a.timestamp, txnNo = _a.txnNo; | ||
return (httpMethod.toUpperCase() + | ||
// url string replacement was dictated by MyInfo docs - no explanation | ||
// was provided for why this is necessary | ||
'&' + | ||
url.replace('.api.gov.sg', '.e.api.gov.sg') + | ||
'&' + | ||
'apex_l2_eg_app_id=' + | ||
appId + | ||
'&' + | ||
'apex_l2_eg_nonce=' + | ||
nonce + | ||
'&' + | ||
'apex_l2_eg_signature_method=SHA256withRSA' + | ||
'&' + | ||
'apex_l2_eg_timestamp=' + | ||
timestamp + | ||
'&' + | ||
'apex_l2_eg_version=1.0' + | ||
'&' + | ||
'attributes=' + | ||
requestedAttributes.join() + | ||
'&' + | ||
'client_id=' + | ||
clientId + | ||
'&' + | ||
'singpassEserviceId=' + | ||
singpassEserviceId + | ||
(txnNo ? '&' + 'txnNo=' + txnNo : '')); | ||
MyInfoGovClient.prototype.extractUinFin = function (accessToken) { | ||
var decoded; | ||
try { | ||
decoded = jsonwebtoken_1.verify(accessToken, this.myInfoPublicKey, { | ||
algorithms: ['RS256'], | ||
}); | ||
} | ||
catch (err) { | ||
throw new errors_1.InvalidTokenSignatureError(err); | ||
} | ||
if (typeof decoded === 'object' && | ||
util_1.hasProp(decoded, 'sub') && | ||
typeof decoded.sub === 'string') { | ||
return decoded.sub; | ||
} | ||
throw new errors_1.WrongAccessTokenShapeError(); | ||
}; | ||
/** | ||
* Returns signature of basestring signed with private key | ||
* @param basestring - Basestring | ||
* @param privateKey - Private key | ||
* @param outputFormat - One of latin1/hex/base64, passed to | ||
* crypto.sign.sign() | ||
* @return Signature of basestring signed with privateKey. | ||
* Retrieves the access token from the Token endpoint. | ||
* @param authCode Authorisation code provided to the redirect endpoint | ||
* @returns The access token as a JWT | ||
* @throws {MyInfoResponseError} Throws if MyInfo returns a non-200 response | ||
* @throws {MissingAccessTokenError} Throws if MyInfo response does not | ||
* contain the access token | ||
*/ | ||
MyInfoGovClient.prototype._signBaseString = function (basestring, privateKey, outputFormat) { | ||
var signer = crypto_1.default.createSign('RSA-SHA256'); | ||
signer.update(basestring); | ||
signer.end(); | ||
return signer.sign(privateKey, outputFormat); | ||
MyInfoGovClient.prototype.getAccessToken = function (authCode) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var postUrl, postParams, headers, response, err_2; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
postUrl = "" + this.baseAPIUrl + Endpoint.Token; | ||
postParams = { | ||
grant_type: 'authorization_code', | ||
code: authCode, | ||
redirect_uri: this.redirectEndpoint, | ||
client_id: this.clientId, | ||
client_secret: this.clientSecret, | ||
}; | ||
headers = { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Cache-Control': 'no-cache', | ||
Authorization: this._generateAuthHeader('POST', postUrl, postParams), | ||
}; | ||
_b.label = 1; | ||
case 1: | ||
_b.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, axios_1.default | ||
// eslint-disable-next-line camelcase | ||
.post(postUrl, util_1.objToSearchParams(postParams), { headers: headers })]; | ||
case 2: | ||
response = _b.sent(); | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
err_2 = _b.sent(); | ||
throw new errors_1.MyInfoResponseError(err_2); | ||
case 4: | ||
if (!((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.access_token) || | ||
typeof response.data.access_token !== 'string') { | ||
throw new errors_1.MissingAccessTokenError(); | ||
} | ||
return [2 /*return*/, response.data.access_token]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Create the APEX authentication header from constituent parts. | ||
* @param realm Realm | ||
* @param appId Client appId | ||
* @param nonce A randomly generated base64 number | ||
* @param signature Signature generated with _signBaseString | ||
* @param timestamp Current timestamp generated with Date.now() | ||
* @return Authentication header to be included in API | ||
* call | ||
* Generates the content of the 'Authorization' header to be sent | ||
* with a request to MyInfo. | ||
* @param method HTTP method to be used for the request | ||
* @param url Endpoint to which the request is being sent | ||
* @param urlParams Query parameters being sent with the request | ||
* @returns The content which should be provided as the Authorization | ||
* header | ||
*/ | ||
MyInfoGovClient.prototype._formulateAuthHeader = function (_a) { | ||
var realm = _a.realm, appId = _a.appId, nonce = _a.nonce, signature = _a.signature, timestamp = _a.timestamp; | ||
return ('Apex_l2_Eg ' + | ||
'realm="' + | ||
realm + | ||
'",' + | ||
'apex_l2_eg_app_id="' + | ||
appId + | ||
'",' + | ||
'apex_l2_eg_nonce="' + | ||
nonce + | ||
'",' + | ||
'apex_l2_eg_signature_method="SHA256withRSA",' + | ||
'apex_l2_eg_signature="' + | ||
signature + | ||
'",' + | ||
'apex_l2_eg_timestamp="' + | ||
timestamp + | ||
'",' + | ||
'apex_l2_eg_version="1.0"'); | ||
MyInfoGovClient.prototype._generateAuthHeader = function (method, url, urlParams) { | ||
var timestamp = String(Date.now()); | ||
var nonce = crypto_1.default.randomBytes(32).toString('base64'); | ||
var authParams = util_1.sortObjKeys(__assign(__assign({}, urlParams), { signature_method: 'RS256', nonce: nonce, | ||
timestamp: timestamp, app_id: this.clientId })); | ||
var paramString = qs_1.default.stringify(authParams, { encode: false }); | ||
var baseString = method.toUpperCase() + "&" + url + "&" + paramString; | ||
var signature = crypto_1.default | ||
.createSign('RSA-SHA256') | ||
.update(baseString) | ||
.sign(this.clientPrivateKey, 'base64'); | ||
return "PKI_SIGN timestamp=\"" + timestamp + "\",nonce=\"" + nonce + "\",app_id=\"" + this.clientId + "\",signature_method=\"RS256\",signature=\"" + signature + "\""; | ||
}; | ||
/** | ||
* Decrypts a JWE response string. | ||
* @param jwe Fullstop-delimited JWE | ||
* @returns The decrypted data, with signature already verified | ||
* @throws {DecryptDataError} Throws if an error occurs while decrypting data | ||
* @throws {InvalidDataSignatureError} Throws if signature on data is invalid | ||
* @throws {WrongDataShapeError} Throws if decrypted data from MyInfo is | ||
* of the wrong type | ||
*/ | ||
MyInfoGovClient.prototype._decryptJWE = function (jwe) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var jwt, decoded, keystore, payload, err_3; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 3, , 4]); | ||
return [4 /*yield*/, node_jose_1.default.JWK.createKeyStore().add(this.clientPrivateKey, 'pem')]; | ||
case 1: | ||
keystore = _a.sent(); | ||
return [4 /*yield*/, node_jose_1.default.JWE.createDecrypt(keystore).decrypt(jwe) | ||
// The JSON.parse here is important, as the payload is wrapped in quotes | ||
]; | ||
case 2: | ||
payload = (_a.sent()).payload; | ||
// The JSON.parse here is important, as the payload is wrapped in quotes | ||
jwt = JSON.parse(payload.toString()); | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
err_3 = _a.sent(); | ||
throw new errors_1.DecryptDataError(err_3); | ||
case 4: | ||
try { | ||
decoded = jsonwebtoken_1.verify(jwt, this.myInfoPublicKey, { | ||
algorithms: ['RS256'], | ||
}); | ||
} | ||
catch (err) { | ||
throw new errors_1.InvalidDataSignatureError(err); | ||
} | ||
if (typeof decoded !== 'object') { | ||
throw new errors_1.WrongDataShapeError(); | ||
} | ||
return [2 /*return*/, decoded]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return MyInfoGovClient; | ||
}()); | ||
exports.MyInfoGovClient = MyInfoGovClient; |
{ | ||
"name": "@opengovsg/myinfo-gov-client", | ||
"version": "2.1.3", | ||
"description": "A lightweight client to easily call the MyInfo TUO endpoint for the Singapore government. Compatible with NodeJS version >=6.", | ||
"version": "3.0.0", | ||
"description": "A lightweight client to easily call the MyInfo Person v3.2 endpoint for the Singapore government. Tested with NodeJS version >=12.", | ||
"main": "build/index.js", | ||
"types": "build/index.d.ts", | ||
"files": [ | ||
"build" | ||
], | ||
"scripts": { | ||
"test": "npm run build && npx nyc --reporter=text mocha --recursive", | ||
"test-unit": "jest", | ||
"test-e2e": "env-cmd -f test/.test-env concurrently --success first --kill-others \"mockpass\" \"testcafe\"", | ||
"test": "npm run test-unit && npm run test-e2e", | ||
"lint": "eslint --ext .ts --ignore-pattern '*.d.ts' --ignore-path .gitignore --fix .", | ||
"build": "tsc", | ||
"build": "tsc -p tsconfig.build.json", | ||
"prepublishOnly": "npm run build" | ||
@@ -28,30 +33,33 @@ }, | ||
"dependencies": { | ||
"axios": "^0.21.0", | ||
"jose": "^0.3.2", | ||
"axios": "^0.21.1", | ||
"jsonwebtoken": "^8.5.1", | ||
"node-jose": "^2.0.0", | ||
"path": "^0.12.7", | ||
"qs": "^6.9.4", | ||
"request": "^2.88.2" | ||
"qs": "^6.9.6" | ||
}, | ||
"devDependencies": { | ||
"@opengovsg/eslint-config-opengovsg": "^1.0.5", | ||
"@opengovsg/mockpass": "^2.6.1", | ||
"@types/express": "^4.17.11", | ||
"@types/jest": "^26.0.20", | ||
"@types/jsonwebtoken": "^8.5.0", | ||
"@types/node": "^14.11.10", | ||
"@types/node-jose": "^1.1.5", | ||
"@types/qs": "^6.9.5", | ||
"@types/request": "^2.48.5", | ||
"@typescript-eslint/eslint-plugin": "^4.4.1", | ||
"@typescript-eslint/parser": "^4.4.1", | ||
"chai": "^4.2.0", | ||
"concurrently": "^5.3.0", | ||
"env-cmd": "^10.1.0", | ||
"eslint": "^7.11.0", | ||
"eslint-config-standard": "^12.0.0", | ||
"eslint-plugin-import": "^2.14.0", | ||
"eslint-plugin-mocha": "^5.2.0", | ||
"eslint-plugin-node": "^8.0.0", | ||
"eslint-plugin-promise": "^4.0.1", | ||
"eslint-plugin-standard": "^4.0.0", | ||
"mocha": "^8.1.3", | ||
"nyc": "^15.1.0", | ||
"proxyquire": "^2.1.0", | ||
"typescript": "^4.0.3" | ||
"express": "^4.17.1", | ||
"jest": "^26.6.3", | ||
"testcafe": "^1.10.1", | ||
"ts-jest": "^26.5.0", | ||
"ts-node": "^9.1.1", | ||
"typescript": "^4.1.5" | ||
} | ||
} | ||
} |
294
README.md
# myinfo-gov-client | ||
A lightweight client to easily call the MyInfo TUO endpoint for the Singapore government. Compatible with NodeJS version >=10. | ||
A lightweight client to easily call the MyInfo Person v3.2 endpoint for the Singapore government. Tested with NodeJS version >=12. | ||
# Quick Start | ||
```javascript | ||
@@ -9,92 +11,228 @@ 'use strict' | ||
const fs = require('fs') | ||
const app = require('express')() | ||
const { | ||
MyInfoGovClient, | ||
CATEGORICAL_DATA_DICT, // Use this to look up code values | ||
} = require('@opengovsg/myinfo-gov-client') | ||
const { MyInfoGovClient } = require('@opengovsg/myinfo-gov-client') | ||
// Application configuration | ||
const APP_DOMAIN = '<Your application domain URL>' | ||
const PORT = 5000 | ||
const REQUESTED_ATTRIBUTES = ['name', 'sex', 'race'] | ||
// Endpoint to which user should be redirected after login | ||
const REDIRECT_ENDPOINT_PATH = '/login' | ||
function main() { | ||
// MyInfo credentials | ||
const clientId = '<Your Client ID>' | ||
const clientSecret = fs.readFileSync('./secrets/clientSecret.txt') | ||
const singpassEserviceId = '<Your SingPass e-Service ID>' | ||
const myInfoPublicKey = fs.readFileSync('./static/myInfoPublicKey.pem') | ||
const clientPrivateKey = fs.readFileSync('./secrets/privateKey.pem') | ||
// Your application configuration | ||
const realm = '<Your Realm>' | ||
const appId = '<Your App ID>' | ||
const clientId = appId; // Usually the same value | ||
const singpassEserviceId = '<Your SingPass e-Service ID>' | ||
// Initialise client | ||
const myInfoGovClient = new MyInfoGovClient({ | ||
clientId, | ||
clientSecret, | ||
singpassEserviceId, | ||
redirectEndpoint: `${APP_DOMAIN}${REDIRECT_ENDPOINT_PATH}`, | ||
clientPrivateKey, | ||
myInfoPublicKey, | ||
mode: 'stg', // Set to 'dev' to call dev endpoint, leave empty for prod | ||
}) | ||
// Used for signing your request basestring with private key | ||
const privateKey = fs.readFileSync('./secrets/privateKey.pem') | ||
app.get('/', (_req, res) => { | ||
const redirectUrl = client.createRedirectURL({ | ||
purpose: 'Information for my application', | ||
requestedAttributes: REQUESTED_ATTRIBUTES, | ||
relayState: 'State to be returned in redirect query parameters', | ||
}) | ||
return res.send(` | ||
<a href=${redirectUrl}>Log in</a> | ||
`) | ||
}) | ||
// MyInfo client | ||
const myInfo = new MyInfoGovClient({ | ||
realm, | ||
appId, | ||
clientId, | ||
singpassEserviceId, | ||
privateKey, | ||
mode: 'stg', // Set to 'dev' to call dev endpoint, leave empty for prod | ||
}); | ||
app.get(REDIRECT_ENDPOINT_PATH, async (req, res) => { | ||
// Authorisation code passed via query parameters | ||
const { code } = req.query | ||
const accessToken = await client.getAccessToken(code) | ||
// Result contains NRIC and MyInfo data | ||
const result = await client.getPerson(accessToken, REQUESTED_ATTRIBUTES) | ||
return res.json(result.data) | ||
}) | ||
// API params | ||
const uinFin = 'S3000024B' // See list of dev/staging NRICs below | ||
const requestedAttributes = [ | ||
'name', | ||
'marriedname', | ||
'hanyupinyinname', | ||
'aliasname', | ||
'hanyupinyinaliasname', | ||
'sex', | ||
'race', | ||
'dialect', | ||
'nationality', | ||
'dob', | ||
'birthcountry', | ||
'secondaryrace', | ||
'residentialstatus', | ||
'passportnumber', | ||
'passportexpirydate', | ||
'email', | ||
'mobileno', | ||
'regadd', | ||
'housingtype', | ||
'hdbtype', | ||
'mailadd', | ||
'billadd', | ||
'marital', | ||
'edulevel', | ||
'marriagecertno', | ||
'countryofmarriage', | ||
'marriagedate', | ||
'divorcedate', | ||
'childrenbirthrecords', | ||
'relationships', | ||
'edulevel', | ||
'gradyear', | ||
'schoolname', | ||
'occupation', | ||
'employment', | ||
'workpassstatus', | ||
'workpassexpirydate', | ||
'householdincome', | ||
'vehno', | ||
]; | ||
const txnNo = 1234 // an optional transaction number | ||
app.listen(PORT, () => console.log(`App listening on port ${PORT}`)) | ||
``` | ||
// API parameters | ||
var params = {uinFin, requestedAttributes, txnNo} | ||
# API | ||
// Make API call | ||
myInfo.getPersonBasic(params) | ||
.then(function(personObject) { | ||
console.log('Results of Person-Basic endpoint:\n', personObject) | ||
}) | ||
.catch(function(error) { | ||
console.log('Error:\n', error) | ||
}) | ||
} | ||
## Constructor | ||
main() | ||
``` | ||
MyInfoGovClient(config: IMyInfoConfig) | ||
``` | ||
### Configuration parameters | ||
Type: `IMyInfoConfig` | ||
**clientId** | ||
Type: `string` | ||
Client ID (also known as App ID). | ||
**clientSecret** | ||
Type: `string | Buffer` | ||
Client secret provided by MyInfo. | ||
**singpassEserviceId** | ||
Type: `string` | ||
The default e-service ID registered with SingPass to use. Can be overridden if necessary in `createRedirectURL` and `getPerson` functions. | ||
**redirectEndpoint** | ||
Type: `string` | ||
Endpoint to which user should be redirected after login. | ||
**clientPrivateKey** | ||
Type: `string | Buffer` | ||
RSA-SHA256 private key, which must correspond with public key provided to MyInfo during the onboarding process. | ||
**myInfoPublicKey** | ||
Type: `string | Buffer` | ||
MyInfo server's public key for verifying their signature | ||
**mode** | ||
Type: `MyInfoMode` | ||
Optional mode, which determines the MyInfo endpoint to call. One of `'dev'`, `'stg'` or `'prod'`. Defaults to `'prod'`. | ||
### Returns | ||
Type: `MyInfoGovClient` | ||
Instance of `MyInfoGovClient`. | ||
## createRedirectUrl | ||
``` | ||
.createRedirectUrl(authRequest) | ||
``` | ||
### Parameters | ||
Type: `IAuthRequest` | ||
**purpose** | ||
Type: `string` | ||
Purpose of requesting the data, which will be shown to user. | ||
**requestedAttributes** | ||
Type: `MyInfoScope[]` | ||
MyInfo scopes which the user must consent to provide. | ||
**relayState** | ||
Type: `string` | ||
Optional state to be forwarded to the redirect endpoint via query parameters. | ||
**singpassEserviceId** | ||
Type: `string` | ||
Optional alternative e-service ID. Defaults to the e-serviceId provided in the constructor. | ||
**redirectEndpoint** | ||
Type: `string` | ||
Optional alternative redirect endpoint. Defaults to the endpoint provided in the constructor. | ||
### Returns | ||
Type: `string` | ||
The URL to which the user should be redirected to log in to SingPass and consent to providing the given attributes. | ||
## getAccessToken | ||
``` | ||
.getAccessToken(authCode) | ||
``` | ||
**authCode** | ||
Type: `string` | ||
Authorisation code given by MyInfo in query parameters. | ||
### Returns | ||
Type: `string` | ||
The access token which can be used to call the Person endpoint. This is a JSON web token containing the user's NRIC. | ||
## getPerson | ||
``` | ||
.getPerson(accessToken, requestedAttributes, singpassEserviceId) | ||
``` | ||
**accessToken** | ||
Type: `string` | ||
The access token retrieved from the Token endpoint using `.getAccessToken`. | ||
**requestedAttributes** | ||
Type: `MyInfoScope[]` | ||
Scopes to request from Myinfo. Should correspond to the scopes provided when initiating SingPass login. | ||
**singpassEserviceId** | ||
Type: `string` | ||
Optional alternative e-service ID. Defaults to the e-serviceId provided in the constructor. | ||
### Returns | ||
Type: `Promise<IPersonResponse>` | ||
An object containing the NRIC/FIN of the user and the attributes retrieved from MyInfo. | ||
## extractUinFin | ||
``` | ||
.extractUinFin(accessToken) | ||
``` | ||
**accessToken** | ||
Type: `string` | ||
The access token retrieved from the Token endpoint using `.getAccessToken`. | ||
### Returns | ||
Type: `string` | ||
The decoded UIN/FIN contained in the access token JWT. The access token signature is verified before the JWT is decoded. | ||
# Available Test accounts | ||
See a list of available MyInfo test accounts [here](docs/TESTACCOUNTS.md). | ||
@@ -101,0 +239,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
4
245
60430
24
13
1241
1
+ Addedjsonwebtoken@^8.5.1
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedjsonwebtoken@8.5.1(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedlodash.includes@4.3.0(transitive)
+ Addedlodash.isboolean@3.0.3(transitive)
+ Addedlodash.isinteger@4.0.4(transitive)
+ Addedlodash.isnumber@3.0.3(transitive)
+ Addedlodash.isplainobject@4.0.6(transitive)
+ Addedlodash.isstring@4.0.1(transitive)
+ Addedlodash.once@4.1.1(transitive)
+ Addedms@2.1.3(transitive)
+ Addedsemver@5.7.2(transitive)
- Removedjose@^0.3.2
- Removedpath@^0.12.7
- Removedrequest@^2.88.2
- Removedajv@6.12.6(transitive)
- Removedasn1@0.2.6(transitive)
- Removedasn1.js@2.2.1(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.0(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedbn.js@2.2.03.3.0(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedinherits@2.0.32.0.4(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjose@0.3.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedminimalistic-assert@1.0.1(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedpath@0.12.7(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.9.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduri-js@4.4.1(transitive)
- Removedutil@0.10.4(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)
Updatedaxios@^0.21.1
Updatedqs@^6.9.6