UNPKG

synt_backend

Version:

Synt light-weight node backend service

664 lines (592 loc) 19.1 kB
var _ = require("lodash"), querystring = require("querystring"), https = require("https"); const db = require("./../mysql/models/index"); class Client { constructor(name) { var defaults = { env: "production", debug: false, prefixDebug: "[keypoint] ", }; this.name = name; // Merge defaults with passed objects this.options = _.merge({}, defaults); this.apiUrl = "demo-api.keypoint.be"; this.loginUrl = "demo-login.keypoint.be"; // Define OAuth object this.oauth = { expires_at: new Date(), token: {}, refresh_token: null, }; return this; } init() { // Get info from database return db.KeypointClient.findOne({ where: { name: this.name } }).then( (client) => { // client options var options = { client_id: client.client_id, client_secret: client.client_secret, env: process.env.NODE_ENV || "development", debug: process.env.NODE_ENV ? false : true, prefixDebug: "[keypoint] ", }; this.apiUrl = process.env.NODE_ENV === "production" ? "demo-api.keypoint.be" : "demo-api.keypoint.be"; this.loginUrl = process.env.NODE_ENV === "production" ? "demo-login.keypoint.be" : "demo-login.keypoint.be"; // Merge defaults with passed objects this.options = _.merge({}, this.options, options); if (this.options.debug) { console.log(this.options.prefixDebug + "Client - Options Added"); } // Define OAuth object let oauth = { expires_at: client.expires_at, token: { access_token: client.access_token }, refresh_token: client.refresh_token, username: client.username, password: client.password, }; this.oauth = _.merge({}, this.oauth, oauth); return this; } ); } updateDatabaseTokens(token) { this.oauth.token = token; this.oauth.refresh_token = token.refresh_token; // FIXME: Date.now() token.expires_at = Date.now() + parseInt(token.expires_in) * 1000; this.oauth.expires_at = token.expires_at; console.log("Token: " + JSON.stringify(token)); if (!token.error) { db.KeypointClient.update( { access_token: token.access_token, expires_at: token.expires_at, refresh_token: token.refresh_token, }, { where: { name: this.name } } ); } else { db.KeypointClient.update( { access_token: null, expires_at: new Date(), refresh_token: null, }, { where: { name: this.name } } ); } } /** * Retrieve oauth token from API endpoint * @param {Function} callback Gets called after request is complete */ token(grantType, callback) { if (this.options.debug) { console.log( this.options.prefixDebug + '.token - send token: "' + grantType + '"' ); } var data = { grant_type: grantType, client_id: this.options.client_id, client_secret: this.options.client_secret, }; switch (grantType) { case "password": data.username = this.oauth.username; data.password = this.oauth.password; break; case "refresh_token": data.refresh_token = this.oauth.refresh_token; break; } var self = this; this.sendRequest("/token", "POST", {}, data, function (err, token) { if (self.options.debug) { console.log( self.options.prefixDebug + ".refresh_token - Retreived token: " + JSON.stringify(token) ); } // strange stuff after refresh if (token.id_token) { token.access_token = token.id_token; } self.updateDatabaseTokens(token); callback(err, token); }); } /** * Checks the client for valid authentication * Generates a new token if needed and possible * @param {Function} cb calllback */ checkAuth(cb) { if (this.options.debug) { console.log(this.options.prefixDebug + ".checkAuth - Checking auth"); } // Check if the client is authorized if (this.oauth.expires_at > Date.now() && this.oauth.token?.access_token) { if (this.options.debug) { console.log( this.options.prefixDebug + ".checkAuth - Client is authorized and token is still valid" ); } // Client is authorized and token is still valid cb(null, true, this.oauth.token); } else if (this.oauth.refresh_token) { if (this.options.debug) { console.log( this.options.prefixDebug + ".checkAuth - Either the token has expired, or the client is not authorized but has a refresh token" ); } // Either the token has expired, or the client is not authorized but has a refresh token // With this info a new token can be requested this.token("refresh_token", function (err, token) { cb(err, true, token); }); } else if (this.oauth.username && this.oauth.password) { if (this.options.debug) { console.log( this.options.prefixDebug + ".checkAuth - Either the token has expired, or the client is not authorized but has username / password" ); } // Either the token has expired, or the client is not authorized but has a refresh token // With this info a new token can be requested this.token("password", function (err, token) { cb(err, true, token); }); } else { if (this.options.debug) { console.log( this.options.prefixDebug + ".checkAuth - No token, refresh token or request code found, this client is in no way authenticated" ); } // No token, refresh token or request code found, this client is in no way authenticated cb(null, false, null); } } /** * Sends a request to an endpoint * @param {string} endpoint API endpoint * @param {string} method HTTP method * @param {object} params URL params * @param {object} data POST data * @param {Function} callback Callback after request is done */ sendRequest(endpoint, method, params, data, callback) { var requiresAuth = !(endpoint === "/token" || endpoint === "/userinfo"), //https://demo-login.keypoint.be/.well-known/openid-configuration self = this, body; // Check arguments if (typeof params === "function") { callback = params; } else if (typeof data === "function") { callback = data; } // if a (POST) data object is passed, stringify it if (!requiresAuth && typeof data === "object") { body = querystring.stringify(data); } else if (requiresAuth && typeof data === "object") { body = JSON.stringify(data); endpoint = encodeURI(endpoint); } else { body = data || null; } // Set headers var headers = { "Cache-Control": "no-cache", Accept: "application/json", }; // If this is a POST, set appropriate headers if (method === "POST" || method === "PUT") { if (body) { headers["Content-Length"] = body.length; } headers["Content-Type"] = "application/json"; // If this is an endpoint that is used to authenticate, set the content type to application/x-www-form-urlencoded if (!requiresAuth) { headers["Content-Type"] = "application/x-www-form-urlencoded"; } } // Wrap the request in a function var doRequest = function () { if (self.options.debug) { console.log(self.options.prefixDebug + ".sendRequest - doRequest"); if (requiresAuth) { console.log(self.options.prefixDebug + ".sendRequest - requiresAuth"); } } // Set request options var options = { host: requiresAuth ? self.apiUrl : self.loginUrl, port: 443, method: method, headers: headers, }; // Stringify URL params var paramString = querystring.stringify(params); // Check if the params object exists and is not empty if (typeof params === "object" && paramString !== "") { // If exists and not empty, add them to the endpoint options.path = [ (requiresAuth ? "/api/v3" : "/connect") + endpoint, "?", paramString, ].join(""); } else { options.path = (requiresAuth ? "/api/v3" : "/connect") + endpoint; } // Make the request var req = https.request(options, function (res) { var responseData = ""; // Set the response to utf-8 encoding res.setEncoding("utf-8"); // Add chunk to responseData res.on("data", function (chunk) { responseData += chunk; }); // Request ended, wrap up res.on("end", function () { try { responseData = JSON.parse(responseData); } catch (e) { // Don't parse responseData console.log("no response data to parse"); } if ( !self.retried && (responseData.Message === "Authorization has been denied for this request." || (typeof responseData === "string" && responseData.indexOf("Forbidden. No access") > -1) || responseData === "") ) { // auth failure try again if (self.options.debug) { console.log( self.options.prefixDebug + ".sendRequest - auth fail - retry" ); } let token = { access_token: null, expires_in: new Date(), refresh_token: null, }; self.retried = true; self.updateDatabaseTokens(token); self.sendRequest(endpoint, method, params, data, callback); } else { callback(null, responseData); } }); }); // Handle API errors req.on("error", function (err, data) { callback(err, data); }); // Write request body if (options.method === "POST" || options.method === "PUT") { req.write(body); } // End request req.end(); }; // Check if auth is required if (requiresAuth) { // Authorization is required, check if auth is valid self.checkAuth(function (err, isValid, token) { if (isValid) { // Set authorization header headers["Authorization"] = "Bearer " + token.access_token; // Make the request doRequest(); } else { // Auth is not valid, return a custom error callback( new Error( "No valid authentication found, please set either a token request code, or a valid refresh token" ), null ); } }); } else { // No authorization is required, just make the request doRequest(); } } createPromise(endpoint, method, params, data) { return new Promise((resolve, reject) => { this.sendRequest( endpoint, method, params, data, function (err, response) { if (err) return reject(err); if (response.Message) { return resolve({ success: false, Message: response.Message, ModelState: response.ModelState, }); } else { return resolve(response); } } ); }); } getInvolvedPartyTypes() { return this.createPromise("/InvolvedPartyType", "GET"); } getCase(id) { return this.createPromise("/case/" + id, "GET"); } getDeclaration(id) { return this.createPromise("/declaration/" + id, "GET"); } getFileContent(id) { return this.createPromise("/file/" + id + "/content", "GET"); } postVMEActor(VME) { const Location = { ...VME.Company.toJSON(), name: "VME " + VME.alias, }; const Address = { street: Location.address.split(" ")[0], number: Location.address.split(" ")[1] || null, nbr: Location.address.split(" ")[1] || null, postalCode: Location.address.split(" ")[2], city: Location.address.split(" ")[3], countryCode: Location.country_code, }; const Synt = VME.Users.find((U) => U.UserVME.type === "synt_authoriser"); // There may be no Synt const data = { actorType: 3, //VME kboNbr: VME.Company.vat_number, establishmentNumber: null, contactInfo: { companyName: VME.Company.name, accountNbr: null, phone: Synt?.phone, mobilePhone: Synt?.phone, email: Synt?.email, contactTitle: " Synt" + VME.alias, contactFirstName: Synt?.first_name, contactLastName: Synt?.last_name, notes: null, language: 0, address: Address, invoiceAddress: Address, contactName: VME.alias, }, fsmaNumber: null, }; return this.createPromise("/actor", "POST", null, data); } postSyntActor(VME) { const Synt = VME.Users.find((U) => U.UserVME.type === "synt_authoriser"); const data = { actorType: 4, //Syndicus kboNbr: null, establishmentNumber: null, contactInfo: { companyName: null, accountNbr: null, phone: Synt.phone, pobilePhone: Synt.phone, email: Synt.email, contactTitle: "Synt " + VME.alias, contactFirstName: Synt?.first_name, contactLastName: Synt?.last_name, notes: null, language: 0, address: null, invoiceAddress: null, contactName: VME.alias, }, }; return this.createPromise("/Actor", "POST", null, data); } async postDeclaration(Incident, VME, Declarer) { if (!VME.actor_id) { let Actor = await this.postVMEActor(VME); VME.actor_id = Actor.id; VME.save(); } const Location = { ...VME.Company.toJSON(), name: "VME " + VME.alias, }; const Address = { street: Location.address.split(" ")[0], number: Location.address.split(" ")[1], nbr: Location.address.split(" ")[1], city: Location.address.split(" ")[3], postalCode: Location.address.split(" ")[2], countryCode: Location.country_code, }; const Synt = VME.Users.find((U) => U.UserVME.type === "synt_authoriser"); const data = { declareTo: 1, insuranceDomain: null, insurancePolicyType: null, policyNumber: null, policyId: null, remark: Incident.report_description, //new Date() + ": " + Incident.report_description + " ", excess: { excessType: "None", excessAmount: 0, excessIncreasedAmount: 0, remark: "", }, coverageConfirmed: false, event: { insuranceCircumstance: null, insuranceGuarantee: null, eventAddress: Address, damageType: "Other", damageDate: new Date(), damageLocation: Location.address, damageCause: `${Incident.is_urgent ? "URGENT: " : ""}${ Incident.ReportType.name } (${Incident.ReportType.category}): ${Incident.report_description}`, policeReportNumber: "", }, parties: [ { actorId: VME.actor_id, reference: "REF" + Incident.id, actorType: 3, // "Homeowner association", kboNbr: VME.Company.vat_number, //CHECK fsmaNbr: null, establishmentNbr: null, isClient: true, comment: null, language: "NL", contactInfo: { title: "Synt VME " + VME.alias, companyName: VME.Company.name, contactFirstName: Synt?.first_name || "[unknown]", contactLastName: Synt?.last_name || "[unknown]", phone: Synt?.phone || "0485660432", mobilePhone: Synt?.phone || "0485660432", emails: [Synt?.email || "help@synt.be"], address: Address, }, }, ], involvedParties: [ { party: { externalReference: null, kboNbr: VME.Company.vat_number, establishmentNbr: null, isClient: false, fsmaNbr: null, comment: null, address: Address, language: "NL", contactInfo: { companyName: VME.alias, title: "Declarant VME " + VME.alias, contactFirstName: Declarer.first_name || "[unknown]", contactLastName: Declarer.last_name || "[unknown]", phone: Declarer.phone || "0485660432", mobilePhone: Declarer.phone || "0485660432", emails: [Declarer.email || "help@synt.be"], }, }, involvedType: null, involvementType: null, }, ], }; /* Declaration => id Webhook => Case is aangemaakt Case => id => caseid === issueid (ClientLinkUrl) CaseType => array (most recent by TimeStamp) VIA REF INTERN vme definieren Email versturen naar de klant is mogelijk => Maar beter via WebHook en notification // Declaration contact (IsTrue) Initieel factuur via email (client) Beter via WebHook om File op te halen - Programmeerwerk voor Tijs */ return this.createPromise("/case", "POST", null, data); } /* postDeclarationRemark(declaration_id, remark) { return this.createPromise( "/declaration/" + declaration_id + "/remark", "POST", remark, remark ); } */ getBase64(url) { return new Promise((resolve, reject) => { var request = require("request").defaults({ encoding: null }); request.get(url, function (error, response, body) { if (!error && response.statusCode == 200) { let data = Buffer.from(body).toString("base64"); resolve(data); } reject(error); }); }); } postCaseFile(case_id, File) { // FIXME: presignedUrl (get then) return this.getBase64(File.presignedUrl) .then((data) => { const file = { fileGroupId: 8, // FOTO fileNameWithExtention: File.original_name, content: data, }; return this.createPromise( "/case/" + case_id + "/file", "POST", null, file ); }) .catch((err) => console.log(err)); } postWebhook(url, triggers) { return this.createPromise("/webhook/register/", "POST", null, { url, triggers, }); } } /** * Client constuctor * @param {Object} options Options object * @return {Client} Returns a new instance of the Client object */ module.exports.createClient = async function (name) { let client = new Client(name); await client.init(); return client; };