@primno/dataverse-auth
Version:
Authenticate to Dataverse and Dynamics 365 with a connection string or OAuth 2.0. Provides a token persistence cache.
2 lines (1 loc) • 8.91 kB
JavaScript
"use strict";var e=require("@azure/msal-node"),t=require("axios"),r=require("@azure/msal-node-extensions"),i=require("@tediousjs/connection-string");class n{constructor(){t.defaults.validateStatus=()=>!0}async sendGetRequestAsync(e,r){const i={method:"get",url:e,headers:r&&r.headers},n=await t(i);return{headers:n.headers,body:n.data,status:n.status}}async sendPostRequestAsync(e,r){const i={method:"post",url:e,data:r&&r.body||"",headers:r&&r.headers},n=await t(i);return{headers:n.headers,body:n.data,status:n.status}}}async function o(e){const t={serviceName:"Primno.DataverseClient",accountName:"MSALCache",dataProtectionScope:r.DataProtectionScope.CurrentUser,usePlaintextFileOnLinux:!1,...e},i=await r.PersistenceCreator.createPersistence(t);return{cachePlugin:new r.PersistenceCachePlugin(i)}}class s{constructor(e,t){this.oAuthOptions=e,this.supportedApplication=t}async getClient(){if(null==this.application){const t=await async function(t){const{credentials:r,persistence:i}=t,s={auth:{clientId:r.clientId??"51f81489-12ee-4a9e-aaae-a2591f45987d",authority:r.authorityUrl,knownAuthorities:[r.authorityUrl]},system:{networkClient:new n,loggerOptions:{loggerCallback(){},logLevel:e.LogLevel.Verbose,piiLoggingEnabled:!1}},cache:i?.enabled?await o(i):void 0};return r.clientSecret||r.clientCertificate?{type:"confidential",client:new e.ConfidentialClientApplication({...s,auth:{...s.auth,clientSecret:r.clientSecret,clientCertificate:r.clientCertificate}})}:{type:"public",client:new e.PublicClientApplication(s)}}(this.oAuthOptions);if(!this.supportedApplication.includes(t.type))throw new Error(`Unsupported application type: ${t.type}`);this.application=t}return this.application.client}async tryGetAccountFromCache(e){if("public"===this.application?.type){const t=this.application.client.getTokenCache();return(await t.getAllAccounts()).find((t=>t.username.toLocaleLowerCase()===e?.toLocaleLowerCase()))}}get url(){return this.oAuthOptions.url}async getToken(){const e=await this.getClient(),{scope:t,userName:r}=this.oAuthOptions.credentials,i=await this.tryGetAccountFromCache(r);if(null!=i){const r=await e.acquireTokenSilent({scopes:[t],account:i});if(null==r)throw new Error("Unable to acquire token silently");return r.accessToken}{const t=await this.acquireToken(e);if(null==t)throw new Error("Unable to acquire token");return t.accessToken}}}class a extends s{constructor(e){if(null==e.deviceCodeCallback)throw new Error("Device code callback is required for device code flow");if(null==e.credentials.userName)throw new Error("Username is required for device code flow to prevent multiple device code requests");super(e,["public"])}async acquireToken(e){const{scope:t}=this.oAuthOptions.credentials,{deviceCodeCallback:r}=this.oAuthOptions,i=await e.acquireTokenByDeviceCode({scopes:[t],deviceCodeCallback:r});if(null!=i&&i.account?.username?.toLowerCase()!==this.oAuthOptions.credentials.userName?.toLowerCase())throw new Error("Device code was not issued for the correct user. Please check your username.");return i}}class c extends s{constructor(e){if(null==e.credentials.userName||null==e.credentials.password)throw new Error("Username and password are required for password flow");super(e,["confidential","public"])}async acquireToken(e){const{scope:t,userName:r,password:i}=this.oAuthOptions.credentials;return await e.acquireTokenByUsernamePassword({scopes:[t],username:r,password:i})}}class u extends s{constructor(e){if(null==e.credentials.clientId||null==e.credentials.clientSecret)throw new Error("Client ID and secret are required for client credential flow");super(e,["confidential"])}async acquireToken(e){const{scope:t}=this.oAuthOptions.credentials;return await e.acquireTokenByClientCredential({scopes:[t]})}}class l{constructor(e){this.tokenProvider=this.getTokenProvider(e)}getTokenProvider(e){switch(e.credentials.grantType){case"device_code":return new a(e);case"password":return new c(e);case"client_credential":return new u(e);default:throw new Error("Invalid grant type")}}get url(){return this.tokenProvider.url}async getToken(){return this.tokenProvider.getToken()}}const p="";function h(e){return null==e||e===p}function d(e,t){for(const r of t)if(null!=e[r.toLowerCase()])return e[r.toLowerCase()]}function w(e){if(null!=e)return"true"===e.toLowerCase()}async function y(e){const r="Bearer",i=t.create({baseURL:e,validateStatus:()=>!0,maxRedirects:0}),n=await i.request({method:"GET",url:"api/discovery/?SDKClientVersion=9.1"});if(null==n.headers)throw new Error("Unable to discover authority");const o=n.headers["www-authenticate"]?.trim();if(o?.startsWith(r)){const e=o.substring(6).split(",").map((e=>{const t=e.trim().split("=");return{key:t[0],value:t[1]}})),t=e.find((e=>"authorization_uri"==e.key))?.value,r=e.find((e=>"resource_id"==e.key))?.value;if(!h(t))return{authUrl:t,resource:r}}throw new Error("Unable to discover authority")}const f=["ServiceUri","Service Uri","Url","Server"],m=["UserName","User Name","UserId","User Id"],g=["Password"],C=["Domain"],v=["HomeRealmUri","Home Realm Uri"],A=["AuthType","AuthenticationType"],T=["RequireNewInstance"],k=["ClientId","AppId","ApplicationId"],P=["RedirectUri","ReplyUrl"],S=["TokenCacheStorePath"],U=["LoginPrompt"],b=["CertificateThumbprint","Thumbprint"],x=["CertificateStoreName","StoreName"],L=["SkipDiscovery"],q=["Integrated Security"],N=["ClientSecret","Secret"];var I,E;exports.AuthenticationType=void 0,(I=exports.AuthenticationType||(exports.AuthenticationType={}))[I.AD=0]="AD",I[I.OAuth=1]="OAuth",I[I.Office365=2]="Office365",I[I.Certificate=3]="Certificate",I[I.ClientSecret=4]="ClientSecret",exports.LoginPromptType=void 0,(E=exports.LoginPromptType||(exports.LoginPromptType={}))[E.Auto=0]="Auto",E[E.Always=1]="Always",E[E.Never=2]="Never";class O{constructor(e){this.connectionString=e;const t=i.parseConnectionString(e);this.authType=this.parseAuthenticationType(d(t,A)),this.serviceUri=d(t,f),this.userName=d(t,m),this.password=d(t,g),this.clientId=d(t,k),this.clientSecret=d(t,N),this.redirectUri=d(t,P),this.domain=d(t,C),this.tokenCacheStorePath=d(t,S),this.certStoreName=d(t,x),this.certThumbprint=d(t,b),this.homeRealmUri=d(t,v),this.requireNewInstance=w(d(t,T)),this.loginPrompt=this.parseLoginPrompt(d(t,U)),this.skipDiscovery=w(d(t,L)),this.integratedSecurity=d(t,q),this.authType==exports.AuthenticationType.OAuth&&h(this.clientId)&&h(this.redirectUri)&&(this.clientId="51f81489-12ee-4a9e-aaae-a2591f45987d",this.redirectUri="app://58145B91-0C36-4500-8554-080854F2AC97")}parseLoginPrompt(e){switch(e?.toLowerCase()){case"auto":return exports.LoginPromptType.Auto;case"always":return exports.LoginPromptType.Always;case"never":return exports.LoginPromptType.Never}}parseAuthenticationType(e){switch(e?.toLowerCase()){case"oauth":return exports.AuthenticationType.OAuth;case"certificate":return exports.AuthenticationType.Certificate;case"clientsecret":return exports.AuthenticationType.ClientSecret;case"office365":return exports.AuthenticationType.Office365;case"ad":return exports.AuthenticationType.AD}}toString(){return this.connectionString}}function D(e){if(null!=e.certStoreName||null!=e.certThumbprint)throw new Error("Certificate authentication is not supported");if(null!=e.userName&&null!=e.password)return"password";if(null!=e.clientSecret)return"client_credential";if(null==e.loginPrompt||e.loginPrompt===exports.LoginPromptType.Always||e.loginPrompt===exports.LoginPromptType.Auto)return"device_code";throw new Error("Unable to choose a grant type")}exports.ConnStringTokenProvider=class{constructor(e,t){if(this.options=t,"string"==typeof e)this.csp=new O(e);else{if(!(e instanceof O))throw new Error("Invalid connection string");this.csp=e}if(null==this.csp.serviceUri)throw new Error("Service URI is missing");if(null==this.csp.authType)throw new Error("Authentication type is missing")}get url(){return this.csp.serviceUri}async getTokenProvider(){if(null==this.tokenProvider)switch(this.csp.authType){case exports.AuthenticationType.OAuth:{const e=await y(this.csp.serviceUri),t=function(e,t){if(null==e.clientId)throw new Error("Connection string is missing client id");return{clientId:e.clientId,grantType:D(e),userName:e.userName,password:e.password,redirectUri:e.redirectUri,clientSecret:e.clientSecret,scope:`${t.resource}/.default`,authorityUrl:t.authUrl.replace("oauth2/authorize","").replace("common","organizations")}}(this.csp,e),r={credentials:t,persistence:{enabled:null!=this.csp.tokenCacheStorePath,cachePath:this.csp.tokenCacheStorePath,...this.options?.oAuth?.persistence},deviceCodeCallback:this.options?.oAuth?.deviceCodeCallback,url:this.csp.serviceUri};this.tokenProvider=new l(r);break}default:throw new Error("Unsupported authentication type")}return this.tokenProvider}async getToken(){const e=await this.getTokenProvider();return await e.getToken()}},exports.ConnectionString=O,exports.OAuthTokenProvider=l,exports.discoverAuthority=y;