Socket
Socket
Sign inDemoInstall

@opengovsg/myinfo-gov-client

Package Overview
Dependencies
44
Maintainers
5
Versions
18
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.1.3 to 3.0.0

build/errors.d.ts

2

build/index.d.ts
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"
}
}
}
# 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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc