diff --git a/packages/backend/src/apps/high-mobility/actions/get-vehicle-location/index.ts b/packages/backend/src/apps/high-mobility/actions/get-vehicle-location/index.ts
new file mode 100644
index 00000000..1e4eab16
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/actions/get-vehicle-location/index.ts
@@ -0,0 +1,15 @@
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Get Vehicle Location',
+ key: 'getVehicleLocation',
+ description: 'Get the location of a vehicle',
+
+ async run($) {
+ const response = await $.http.get(
+ `https://sandbox.rest-api.high-mobility.com/v5/vehicle_location`
+ );
+
+ $.setActionItem({ raw: response.data });
+ },
+});
diff --git a/packages/backend/src/apps/high-mobility/actions/index.ts b/packages/backend/src/apps/high-mobility/actions/index.ts
new file mode 100644
index 00000000..73a09721
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/actions/index.ts
@@ -0,0 +1,3 @@
+import getVehicleLocation from './get-vehicle-location';
+
+export default [getVehicleLocation];
diff --git a/packages/backend/src/apps/high-mobility/assets/favicon.svg b/packages/backend/src/apps/high-mobility/assets/favicon.svg
new file mode 100644
index 00000000..83e498bf
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/assets/favicon.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/backend/src/apps/high-mobility/auth/generate-auth-url.ts b/packages/backend/src/apps/high-mobility/auth/generate-auth-url.ts
new file mode 100644
index 00000000..bb3ee012
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/auth/generate-auth-url.ts
@@ -0,0 +1,21 @@
+import { IField, IGlobalVariable } from '@automatisch/types';
+import { URLSearchParams } from 'url';
+
+export default async function generateAuthUrl($: IGlobalVariable) {
+ const oauthRedirectUrlField = $.app.auth.fields.find(
+ (field: IField) => field.key == 'oAuthRedirectUrl'
+ );
+ const redirectUri = oauthRedirectUrlField.value as string;
+
+ const searchParams = new URLSearchParams({
+ response_type: 'code',
+ client_id: $.auth.data.clientId as string,
+ redirect_uri: redirectUri,
+ });
+
+ const url = `https://sandbox.owner-panel.high-mobility.com/oauth/new?${searchParams.toString()}`;
+
+ await $.auth.set({
+ url,
+ });
+}
diff --git a/packages/backend/src/apps/high-mobility/auth/index.ts b/packages/backend/src/apps/high-mobility/auth/index.ts
new file mode 100644
index 00000000..9538cd2c
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/auth/index.ts
@@ -0,0 +1,93 @@
+import generateAuthUrl from './generate-auth-url';
+import verifyCredentials from './verify-credentials';
+import isStillVerified from './is-still-verified';
+import refreshToken from './refresh-token';
+
+export default {
+ fields: [
+ {
+ key: 'oAuthRedirectUrl',
+ label: 'OAuth Redirect URL',
+ type: 'string' as const,
+ required: true,
+ readOnly: true,
+ value: '{WEB_APP_URL}/app/high-mobility/connections/add',
+ placeholder: null,
+ description:
+ 'When asked to input an OAuth callback or redirect URL in High Mobility OAuth, enter the URL above.',
+ clickToCopy: true,
+ },
+ {
+ key: 'screenName',
+ label: 'Screen Name',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description:
+ 'Screen name of your connection to be used on Automatisch UI.',
+ clickToCopy: false,
+ },
+ {
+ key: 'clientId',
+ label: 'Client ID',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Client ID of your High Mobility OAuth app.',
+ clickToCopy: false,
+ },
+ {
+ key: 'clientSecret',
+ label: 'Client Secret',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Client Secret of your High Mobility OAuth app.',
+ clickToCopy: false,
+ },
+ {
+ key: 'privateKey',
+ label: 'Private Key',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Private Key of your High Mobility OAuth app.',
+ clickToCopy: false,
+ },
+ {
+ key: 'appId',
+ label: 'App ID',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'App ID of your High Mobility OAuth app.',
+ clickToCopy: false,
+ },
+ {
+ key: 'clientSerialNumber',
+ label: 'Client Serial Number',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Client Serial Number of your High Mobility OAuth app.',
+ clickToCopy: false,
+ },
+ ],
+
+ generateAuthUrl,
+ verifyCredentials,
+ isStillVerified,
+ refreshToken,
+};
diff --git a/packages/backend/src/apps/high-mobility/auth/is-still-verified.ts b/packages/backend/src/apps/high-mobility/auth/is-still-verified.ts
new file mode 100644
index 00000000..3dbe9cc3
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/auth/is-still-verified.ts
@@ -0,0 +1,9 @@
+import { IGlobalVariable } from '@automatisch/types';
+import getVehicleInfo from '../common/get-vehicle-info';
+
+const isStillVerified = async ($: IGlobalVariable) => {
+ const user = await getVehicleInfo($);
+ return !!user;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/high-mobility/auth/refresh-token.ts b/packages/backend/src/apps/high-mobility/auth/refresh-token.ts
new file mode 100644
index 00000000..43c3db19
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/auth/refresh-token.ts
@@ -0,0 +1,27 @@
+import { IGlobalVariable } from '@automatisch/types';
+
+const refreshToken = async ($: IGlobalVariable) => {
+ const payload = {
+ client_id: $.auth.data.clientId as string,
+ client_secret: $.auth.data.clientSecret as string,
+ refresh_token: $.auth.data.refreshToken as string,
+ grant_type: 'refresh_token',
+ };
+
+ const { data } = await $.http.post(
+ 'https://sandbox.api.high-mobility.com/v1/access_tokens',
+ payload
+ );
+
+ await $.auth.set({
+ tokenType: data.token_type,
+ status: data.status,
+ scope: data.scope,
+ refreshToken: data.refresh_token,
+ expiresIn: data.expires_in,
+ accessToken: data.access_token,
+ authorizationId: data.authorization_id,
+ });
+};
+
+export default refreshToken;
diff --git a/packages/backend/src/apps/high-mobility/auth/verify-credentials.ts b/packages/backend/src/apps/high-mobility/auth/verify-credentials.ts
new file mode 100644
index 00000000..6bf96b83
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/auth/verify-credentials.ts
@@ -0,0 +1,36 @@
+import { IGlobalVariable, IField } from '@automatisch/types';
+import { URLSearchParams } from 'url';
+
+const verifyCredentials = async ($: IGlobalVariable) => {
+ const oauthRedirectUrlField = $.app.auth.fields.find(
+ (field: IField) => field.key == 'oAuthRedirectUrl'
+ );
+ const redirectUri = oauthRedirectUrlField.value as string;
+
+ const payload = {
+ client_id: $.auth.data.clientId as string,
+ client_secret: $.auth.data.clientSecret as string,
+ code: $.auth.data.code as string,
+ redirect_uri: redirectUri,
+ grant_type: 'authorization_code',
+ };
+
+ const response = await $.http.post(
+ 'https://sandbox.api.high-mobility.com/v1/access_tokens',
+ payload
+ );
+
+ const responseData = Object.fromEntries(new URLSearchParams(response.data));
+
+ await $.auth.set({
+ tokenType: responseData.token_type,
+ status: responseData.status,
+ scope: responseData.scope,
+ refreshToken: responseData.refresh_token,
+ expiresIn: responseData.expires_in,
+ accessToken: responseData.access_token,
+ authorizationId: responseData.authorization_id,
+ });
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/high-mobility/common/add-auth-header.ts b/packages/backend/src/apps/high-mobility/common/add-auth-header.ts
new file mode 100644
index 00000000..b2cebae8
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/common/add-auth-header.ts
@@ -0,0 +1,41 @@
+import { TBeforeRequest } from '@automatisch/types';
+import jwt from 'jsonwebtoken';
+import { v4 as uuidv4 } from 'uuid';
+
+const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
+ const { accessToken } = $.auth.data;
+ const { url } = requestConfig;
+
+ if (accessToken && url === '/v1/vehicleinfo') {
+ requestConfig.headers.Authorization = `Bearer ${accessToken}`;
+ return requestConfig;
+ }
+
+ const config = {
+ version: '3.0',
+ type: 'rest_api',
+ private_key: ($.auth.data.privateKey as string).replaceAll('\\n', '\n'),
+ app_uri: 'https://sandbox.rest-api.high-mobility.com/v5',
+ app_id: $.auth.data.appId as string,
+ client_serial_number: $.auth.data.clientSerialNumber as string,
+ };
+
+ const payload = {
+ ver: config.version,
+ aud: config.app_uri,
+ iss: config.client_serial_number,
+ iat: Math.round(Date.now() / 1000),
+ jti: uuidv4(),
+ sub: $.auth.data.accessToken,
+ };
+
+ const priv = Buffer.from(config.private_key, 'utf8');
+
+ const token = jwt.sign(payload, priv, { algorithm: 'ES256' });
+
+ requestConfig.headers.Authorization = `Bearer ${token}`;
+
+ return requestConfig;
+};
+
+export default addAuthHeader;
diff --git a/packages/backend/src/apps/high-mobility/common/get-vehicle-info.ts b/packages/backend/src/apps/high-mobility/common/get-vehicle-info.ts
new file mode 100644
index 00000000..991c73d0
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/common/get-vehicle-info.ts
@@ -0,0 +1,13 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+const getVehicleInfo = async ($: IGlobalVariable): Promise => {
+ const response = await $.http.get(
+ 'https://sandbox.api.high-mobility.com/v1/vehicleinfo'
+ );
+
+ const currentVehicle = response.data;
+
+ return currentVehicle;
+};
+
+export default getVehicleInfo;
diff --git a/packages/backend/src/apps/high-mobility/index.ts b/packages/backend/src/apps/high-mobility/index.ts
new file mode 100644
index 00000000..6f6688ce
--- /dev/null
+++ b/packages/backend/src/apps/high-mobility/index.ts
@@ -0,0 +1,18 @@
+import defineApp from '../../helpers/define-app';
+import addAuthHeader from './common/add-auth-header';
+import auth from './auth';
+import actions from './actions';
+
+export default defineApp({
+ name: 'High Mobility',
+ key: 'high-mobility',
+ iconUrl: '{BASE_URL}/apps/high-mobility/assets/favicon.svg',
+ authDocUrl: 'https://automatisch.io/docs/apps/high-mobility/connection',
+ supportsConnections: true,
+ baseUrl: 'https://high-mobility.com',
+ apiBaseUrl: 'https://api.high-mobility.com',
+ primaryColor: '000000',
+ beforeRequest: [addAuthHeader],
+ auth,
+ actions,
+});