Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanslatten committed Oct 23, 2024
1 parent 0ba8568 commit 8babd64
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 25 deletions.
5 changes: 4 additions & 1 deletion plugins/arcgis/service/src/ArcGISConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ export interface UsernamePasswordAuthConfig {
* Contains OAuth authentication configuration.
*/
export interface OAuthAuthConfig {

type: AuthType.OAuth

/**
* The Client Id for OAuth
*/
clientId: string

/**
* The redirectUri for OAuth
*/
Expand All @@ -121,7 +124,7 @@ export interface OAuthAuthConfig {
*/
authToken?: string

/**
/**
* The expiration date for the temporary token
*/
authTokenExpires?: number
Expand Down
44 changes: 31 additions & 13 deletions plugins/arcgis/service/src/ArcGISIdentityManagerFactory.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ArcGISIdentityManager } from "@esri/arcgis-rest-request"
import { ArcGISAuthConfig, AuthType, FeatureServiceConfig, OAuthAuthConfig, TokenAuthConfig, UsernamePasswordAuthConfig } from './ArcGISConfig'
import { HttpClient } from "./HttpClient";
import { ObservationProcessor } from "./ObservationProcessor";

interface ArcGISIdentityManagerFactory {
create(portal: string, server: string, config: ArcGISAuthConfig, httpClient?: HttpClient): Promise<ArcGISIdentityManager>
create(portal: string, server: string, config: ArcGISAuthConfig, httpClient?: HttpClient, processor?: ObservationProcessor): Promise<ArcGISIdentityManager>
}

const OAuthIdentityManagerFactory: ArcGISIdentityManagerFactory = {
async create(portal: string, server: string, auth: OAuthAuthConfig, httpClient: HttpClient): Promise<ArcGISIdentityManager> {
async create(portal: string, server: string, auth: OAuthAuthConfig, httpClient: HttpClient, processor: ObservationProcessor): Promise<ArcGISIdentityManager> {
console.debug('Client ID provided for authentication')
const { clientId, authToken, authTokenExpires, refreshToken, refreshTokenExpires } = auth

Expand All @@ -22,14 +23,30 @@ const OAuthIdentityManagerFactory: ArcGISIdentityManagerFactory = {
} else if (refreshToken && new Date(refreshTokenExpires || 0) > new Date()) {
// TODO: find a way without using constructor nor httpClient
const url = `${portal}/oauth2/token?client_id=${clientId}&refresh_token=${refreshToken}&grant_type=refresh_token`
const response = await httpClient.sendGet(url)
// TODO: error handling
return ArcGISIdentityManager.fromToken({
clientId: clientId,
token: response.access_token,
portal: portal
});
// TODO: update authToken to new token
try {
const response = await httpClient.sendGet(url)
// Update authToken to new token
const config = await processor.safeGetConfig();
let service = config.featureServices.find(service => service.url === portal)?.auth as OAuthAuthConfig;
const date = new Date();
date.setSeconds(date.getSeconds() + response.expires_in || 0);
service = {
...service,
authToken: response.access_token,
authTokenExpires: date.getTime()
}

await processor.putConfig(config)
return ArcGISIdentityManager.fromToken({
clientId: clientId,
token: response.access_token,
tokenExpires: date,
portal: portal
});
} catch (error) {
throw new Error('Error occurred when using refresh token')
}

} else {
// TODO the config, we need to let the user know UI side they need to authenticate again
throw new Error('Refresh token missing or expired')
Expand All @@ -46,7 +63,7 @@ const TokenIdentityManagerFactory: ArcGISIdentityManagerFactory = {
server: server,
// TODO: what do we really want to do here? esri package seems to need this optional parameter.
// Use authTokenExpires if defined, otherwise set to now plus a day
tokenExpires: auth.authTokenExpires ? new Date(auth.authTokenExpires) : new Date(Date.now() + 24 * 60 * 60 * 1000)
tokenExpires: auth.authTokenExpires ? new Date(auth.authTokenExpires) : new Date(Date.now() + 24 * 60 * 60 * 1000)
})
return identityManager
}
Expand All @@ -68,7 +85,8 @@ const authConfigMap: { [key: string]: ArcGISIdentityManagerFactory } = {

export function getIdentityManager(
config: FeatureServiceConfig,
httpClient: HttpClient // TODO remove in favor of an open source lib like axios
httpClient: HttpClient, // TODO remove in favor of an open source lib like axios
processor: ObservationProcessor
): Promise<ArcGISIdentityManager> {
const auth = config.auth
const authType = config.auth?.type
Expand All @@ -79,7 +97,7 @@ export function getIdentityManager(
if (!factory) {
throw new Error(`No factory found for type ${authType}`)
}
return factory.create(getPortalUrl(config.url), getServerUrl(config.url), auth, httpClient)
return factory.create(getPortalUrl(config.url), getServerUrl(config.url), auth, httpClient, processor)
}


Expand Down
6 changes: 3 additions & 3 deletions plugins/arcgis/service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
}

await processor.putConfig(config)

// TODO: This seems like a bad idea to send the access tokens to the front end. It has no use for them and could potentially be a security concern
res.send(`
<html>
<head>
Expand Down Expand Up @@ -181,7 +181,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
try {
const httpClient = new HttpClient(console)
// Create the IdentityManager instance to validate credentials
await getIdentityManager(service!, httpClient)
await getIdentityManager(service!, httpClient, processor)
let existingService = config.featureServices.find(service => service.url === url)
if (existingService) {
existingService = { ...existingService }
Expand All @@ -206,7 +206,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {

const httpClient = new HttpClient(console)
try {
const identityManager = await getIdentityManager(featureService, httpClient)
const identityManager = await getIdentityManager(featureService, httpClient, processor)
const response = await request(url, {
authentication: identityManager
})
Expand Down
18 changes: 10 additions & 8 deletions plugins/arcgis/web-app/projects/main/src/lib/arc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@ export class ArcService implements ArcServiceInterface {
const oauthWindow = window.open(url, "_blank");

const listener = (event: any) => {
window.removeEventListener('message', listener, false);

if (event.origin !== window.location.origin) {
subject.error('target origin mismatch')
if (event.data.url) {
window.removeEventListener('message', listener, false);

if (event.origin !== window.location.origin) {
subject.error('target origin mismatch')
}

subject.next(event.data)

oauthWindow?.close();
}

subject.next(event.data)

oauthWindow?.close();
}

window.addEventListener('message', listener, false);
Expand Down

0 comments on commit 8babd64

Please sign in to comment.