Skip to content

Commit 1feca85

Browse files
EliMoshkovichTabintel
authored andcommitted
Adding support for EU region (permitio#137)
* Adding support for EU region * Tests added * Fix OAuth Login Bug, API Key Validation Bug, Terraform Export Bug, Infinite Loop Bug with policy create simple, Missing Actions Bug * Default roles added for exporter
1 parent c376723 commit 1feca85

27 files changed

Lines changed: 964 additions & 198 deletions

source/commands/env/export/generators/RoleGenerator.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import { fileURLToPath } from 'url';
88
const __filename = fileURLToPath(import.meta.url);
99
const __dirname = dirname(__filename);
1010

11-
// Define default global roles that should be excluded from creation
12-
// These roles already exist in Permit by default
13-
const DEFAULT_GLOBAL_ROLES = ['admin', 'editor', 'viewer'];
14-
1511
interface RoleData {
1612
key: string;
1713
terraformId: string;
@@ -183,10 +179,8 @@ export class RoleGenerator implements HCLGenerator {
183179
): string {
184180
let terraformId = roleKey;
185181

186-
const isDefaultRole = DEFAULT_GLOBAL_ROLES.includes(roleKey);
187-
188-
// For duplicate roles or default roles, use resource__role format
189-
if (isDuplicate || isDefaultRole || this.usedTerraformIds.has(roleKey)) {
182+
// For duplicate roles, use resource__role format
183+
if (isDuplicate || this.usedTerraformIds.has(roleKey)) {
190184
terraformId = resourceKey
191185
? `${resourceKey}__${roleKey}`
192186
: `global_${roleKey}`;
@@ -300,13 +294,6 @@ export class RoleGenerator implements HCLGenerator {
300294
const validRoles: RoleData[] = [];
301295

302296
for (const role of roles) {
303-
// Skip default global roles that already exist in the system
304-
if (DEFAULT_GLOBAL_ROLES.includes(role.key)) {
305-
// Still add to the ID map for role derivations
306-
this.roleIdMap.set(role.key, role.key);
307-
continue;
308-
}
309-
310297
// Generate Terraform ID
311298
const terraformId = this.generateTerraformId(role.key);
312299

source/commands/env/export/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { WarningCollector } from './types.js';
2+
import { getPermitApiUrl } from '../../../config.js';
23

34
export function createSafeId(...parts: string[]): string {
45
return parts
@@ -36,7 +37,7 @@ variable "PERMIT_API_KEY" {
3637
}
3738
3839
provider "permitio" {
39-
api_url = "https://api.permit.io"
40+
api_url = "${getPermitApiUrl()}"
4041
api_key = var.PERMIT_API_KEY
4142
}
4243
`;

source/commands/login.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import React, { useCallback, useEffect, useState } from 'react';
22
import { Text } from 'ink';
33
import { type infer as zInfer, object, string } from 'zod';
44
import { option } from 'pastel';
5-
import { saveAuthToken } from '../lib/auth.js';
5+
import { saveAuthToken, saveRegion } from '../lib/auth.js';
6+
import { setRegion } from '../config.js';
67
import LoginFlow from '../components/LoginFlow.js';
78
import EnvironmentSelection, {
89
ActiveState,
@@ -25,6 +26,14 @@ export const options = object({
2526
description: 'Use predefined workspace to Login',
2627
}),
2728
),
29+
region: string()
30+
.optional()
31+
.describe(
32+
option({
33+
description: 'Permit region: us or eu (default: us)',
34+
alias: 'r',
35+
}),
36+
),
2837
});
2938

3039
type Props = {
@@ -38,9 +47,14 @@ type Props = {
3847
};
3948

4049
export default function Login({
41-
options: { apiKey, workspace },
50+
options: { apiKey, workspace, region },
4251
loginSuccess,
4352
}: Props) {
53+
// Set region IMMEDIATELY before anything else (synchronously)
54+
if (region && (region === 'us' || region === 'eu')) {
55+
setRegion(region as 'us' | 'eu');
56+
}
57+
4458
const [state, setState] = useState<'login' | 'signup' | 'env' | 'done'>(
4559
'login',
4660
);
@@ -51,6 +65,13 @@ export default function Login({
5165
const [organization, setOrganization] = useState<string>('');
5266
const [environment, setEnvironment] = useState<string>('');
5367

68+
// Save region to keystore after successful login
69+
useEffect(() => {
70+
if (region && (region === 'us' || region === 'eu')) {
71+
saveRegion(region as 'us' | 'eu');
72+
}
73+
}, [region]);
74+
5475
const onEnvironmentSelectSuccess = useCallback(
5576
async (
5677
organisation: ActiveState,

source/components/AuthProvider.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import React, {
1313
useState,
1414
} from 'react';
1515
import { Text, Newline } from 'ink';
16-
import { loadAuthToken } from '../lib/auth.js';
16+
import { loadAuthToken, loadRegion } from '../lib/auth.js';
1717
import Login from '../commands/login.js';
1818
import {
1919
ApiKeyCreate,
@@ -131,6 +131,11 @@ export function AuthProvider({
131131
redirect_scope: 'organization' | 'project' | 'login',
132132
) => {
133133
try {
134+
// Load region from storage BEFORE validating API key
135+
await loadRegion().catch(() => {
136+
// Ignore errors - will default to 'us'
137+
});
138+
134139
const token = await loadAuthToken();
135140
const {
136141
valid,
@@ -188,6 +193,11 @@ export function AuthProvider({
188193
useEffect(() => {
189194
if (state === 'validate') {
190195
(async () => {
196+
// Load region from storage BEFORE validating API key
197+
await loadRegion().catch(() => {
198+
// Ignore errors - will default to 'us'
199+
});
200+
191201
const {
192202
valid,
193203
scope: keyScope,

source/components/policy/CreateSimpleWizard.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export default function CreateSimpleWizard({
3030
const parsedActions = useParseActions(presentActions);
3131
const parsedRoles = useParseRoles(presentRoles);
3232

33+
// Track if preset data has been processed
34+
const [hasProcessedPresetData, setHasProcessedPresetData] =
35+
React.useState(false);
36+
3337
// Initialize step based on preset values
3438
const getInitialStep = () => {
3539
if (presentResources && presentActions && presentRoles) return 'complete';
@@ -109,10 +113,13 @@ export default function CreateSimpleWizard({
109113
};
110114

111115
const handleRolesComplete = useCallback(
112-
async (roles: components['schemas']['RoleCreate'][]) => {
116+
async (
117+
roles: components['schemas']['RoleCreate'][],
118+
resourcesToCreate?: components['schemas']['ResourceCreate'][],
119+
) => {
113120
setStatus('processing');
114121
try {
115-
await createBulkResources(resources);
122+
await createBulkResources(resourcesToCreate || resources);
116123
await createBulkRoles(roles);
117124
setStatus('success');
118125
setResources([]);
@@ -125,6 +132,9 @@ export default function CreateSimpleWizard({
125132
);
126133

127134
useEffect(() => {
135+
// Only process preset data once
136+
if (hasProcessedPresetData) return;
137+
128138
const processPresetData = async () => {
129139
if (presentResources && presentActions && presentRoles) {
130140
try {
@@ -133,7 +143,8 @@ export default function CreateSimpleWizard({
133143
actions: parsedActions,
134144
}));
135145
setResources(resourcesWithActions);
136-
await handleRolesComplete(parsedRoles);
146+
setHasProcessedPresetData(true);
147+
await handleRolesComplete(parsedRoles, resourcesWithActions);
137148
} catch (err) {
138149
handleError((err as Error).message);
139150
}
@@ -143,19 +154,24 @@ export default function CreateSimpleWizard({
143154
actions: parsedActions,
144155
}));
145156
setResources(resourcesWithActions);
157+
setHasProcessedPresetData(true);
146158
}
147159
};
148160

149-
processPresetData();
161+
if (
162+
(presentResources && presentActions && presentRoles) ||
163+
(presentResources && presentActions)
164+
) {
165+
processPresetData();
166+
}
167+
// eslint-disable-next-line react-hooks/exhaustive-deps
150168
}, [
151169
presentResources,
152170
presentActions,
153171
presentRoles,
154172
parsedResources,
155173
parsedActions,
156174
parsedRoles,
157-
handleRolesComplete,
158-
handleError,
159175
]);
160176

161177
return (

source/components/policy/create/TerraformGenerator.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PolicyData } from './types.js';
2+
import { getPermitApiUrl } from '../../../config.js';
23

34
interface TerraformGeneratorProps {
45
tableData: PolicyData;
@@ -37,7 +38,7 @@ export const generateTerraform = ({
3738
}
3839
3940
provider "permitio" {
40-
api_url = "https://api.permit.io"
41+
api_url = "${getPermitApiUrl()}"
4142
api_key = "${authToken}"
4243
}
4344

source/config.ts

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,93 @@
11
export const KEY_FILE_PATH = './permit.key';
22
export const KEYSTORE_PERMIT_SERVICE_NAME = 'Permit.io';
33
export const DEFAULT_PERMIT_KEYSTORE_ACCOUNT = 'PERMIT_DEFAULT_ENV';
4-
export const CLOUD_PDP_URL = 'https://cloudpdp.api.permit.io';
5-
export const PERMIT_API_URL = 'https://api.permit.io';
6-
export const PERMIT_API_STATISTICS_URL =
7-
'https://pdp-statistics.api.permit.io/v2/stats';
8-
export const API_URL = 'https://api.permit.io/v2/';
9-
export const FACTS_API_URL = `${API_URL}facts/`;
10-
export const API_PDPS_CONFIG_URL = `${API_URL}pdps/me/config`;
11-
export const PERMIT_ORIGIN_URL = 'https://app.permit.io';
4+
export const REGION_KEYSTORE_ACCOUNT = 'PERMIT_REGION';
5+
6+
// Region type
7+
export type PermitRegion = 'us' | 'eu';
8+
9+
// Get region from environment variable or default to 'us'
10+
let currentRegion: PermitRegion =
11+
(process.env['PERMIT_REGION'] as PermitRegion) || 'us';
12+
13+
// Function to set the current region
14+
export const setRegion = (region: PermitRegion) => {
15+
currentRegion = region;
16+
};
17+
18+
// Function to get the current region
19+
export const getRegion = (): PermitRegion => {
20+
return currentRegion;
21+
};
22+
23+
// Function to get region-specific subdomain
24+
const getRegionSubdomain = (region: PermitRegion): string => {
25+
return region === 'eu' ? 'eu.' : '';
26+
};
27+
28+
// Region-aware URL getters
29+
export const getPermitApiUrl = (): string => {
30+
const subdomain = getRegionSubdomain(currentRegion);
31+
return `https://api.${subdomain}permit.io`;
32+
};
33+
34+
export const getPermitOriginUrl = (): string => {
35+
const subdomain = getRegionSubdomain(currentRegion);
36+
return `https://app.${subdomain}permit.io`;
37+
};
38+
39+
export const getAuthPermitDomain = (): string => {
40+
const subdomain = getRegionSubdomain(currentRegion);
41+
return `app.${subdomain}permit.io`;
42+
};
43+
44+
export const getCloudPdpUrl = (): string => {
45+
if (currentRegion === 'eu') {
46+
return 'https://cloudpdp.api.eu-central-1.permit.io';
47+
}
48+
return 'https://cloudpdp.api.permit.io';
49+
};
50+
51+
export const getPermitApiStatisticsUrl = (): string => {
52+
if (currentRegion === 'eu') {
53+
return 'https://pdp-statistics.api.eu-central-1.permit.io/v2/stats';
54+
}
55+
return 'https://pdp-statistics.api.permit.io/v2/stats';
56+
};
57+
58+
export const getApiUrl = (): string => {
59+
return `${getPermitApiUrl()}/v2/`;
60+
};
61+
62+
export const getFactsApiUrl = (): string => {
63+
return `${getApiUrl()}facts/`;
64+
};
65+
66+
export const getApiPdpsConfigUrl = (): string => {
67+
return `${getApiUrl()}pdps/me/config`;
68+
};
69+
70+
export const getAuthApiUrl = (): string => {
71+
return `${getPermitApiUrl()}/v1/`;
72+
};
73+
74+
// Legacy exports (maintain backwards compatibility)
75+
export const CLOUD_PDP_URL = getCloudPdpUrl();
76+
export const PERMIT_API_URL = getPermitApiUrl();
77+
export const PERMIT_API_STATISTICS_URL = getPermitApiStatisticsUrl();
78+
export const API_URL = getApiUrl();
79+
export const FACTS_API_URL = getFactsApiUrl();
80+
export const API_PDPS_CONFIG_URL = getApiPdpsConfigUrl();
81+
export const PERMIT_ORIGIN_URL = getPermitOriginUrl();
82+
export const AUTH_PERMIT_DOMAIN = getAuthPermitDomain();
83+
export const AUTH_API_URL = getAuthApiUrl();
1284

1385
export const AUTH_REDIRECT_HOST = 'localhost';
1486
export const AUTH_REDIRECT_PORT = 62419;
1587
export const AUTH_REDIRECT_URI = `http://${AUTH_REDIRECT_HOST}:${AUTH_REDIRECT_PORT}`;
16-
export const AUTH_PERMIT_DOMAIN = 'app.permit.io';
17-
export const AUTH_API_URL = 'https://api.permit.io/v1/';
88+
// auth.permit.io is common for both regions
1889
export const AUTH_PERMIT_URL = 'https://auth.permit.io';
90+
export const AUTH0_AUDIENCE = 'https://api.permit.io/v1/'; // Auth0 audience is shared across all regions
1991

2092
export const TERRAFORM_PERMIT_URL =
2193
'https://permit-cli-terraform.up.railway.app';

source/hooks/export/PermitSDK.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Permit } from 'permitio';
22
import React from 'react';
3+
import { getPermitApiUrl } from '../../config.js';
34

45
export const usePermitSDK = (
56
token: string,
@@ -10,6 +11,7 @@ export const usePermitSDK = (
1011
new Permit({
1112
token,
1213
pdp: pdpUrl,
14+
apiUrl: getPermitApiUrl(),
1315
}),
1416
[token, pdpUrl],
1517
);

source/hooks/useClient.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { paths } from '../lib/api/v1.js';
2-
import { CLOUD_PDP_URL, PERMIT_API_URL, PERMIT_ORIGIN_URL } from '../config.js';
2+
import {
3+
getCloudPdpUrl,
4+
getPermitApiUrl,
5+
getPermitOriginUrl,
6+
} from '../config.js';
37
import type { paths as PdpPaths } from '../lib/api/pdp-v1.js';
48

59
import createClient, {
@@ -308,10 +312,10 @@ const useClient = () => {
308312

309313
const authenticatedApiClient = useCallback(() => {
310314
const client = createClient<paths>({
311-
baseUrl: PERMIT_API_URL,
315+
baseUrl: getPermitApiUrl(),
312316
headers: {
313317
Accept: '*/*',
314-
Origin: PERMIT_ORIGIN_URL,
318+
Origin: getPermitOriginUrl(),
315319
'Content-Type': 'application/json',
316320
Authorization: `Bearer ${globalTokenGetterSetter.tokenGetter()}`,
317321
},
@@ -321,7 +325,7 @@ const useClient = () => {
321325

322326
const authenticatedPdpClient = useCallback((pdp_url?: string) => {
323327
const client = createClient<PdpPaths>({
324-
baseUrl: pdp_url ?? CLOUD_PDP_URL,
328+
baseUrl: pdp_url ?? getCloudPdpUrl(),
325329
headers: {
326330
Accept: '*/*',
327331
'Content-Type': 'application/json',
@@ -503,10 +507,10 @@ const useClient = () => {
503507
cookie?: string | null,
504508
) => {
505509
const client = createClient<paths>({
506-
baseUrl: PERMIT_API_URL,
510+
baseUrl: getPermitApiUrl(),
507511
headers: {
508512
Accept: '*/*',
509-
Origin: PERMIT_ORIGIN_URL,
513+
Origin: getPermitOriginUrl(),
510514
'Content-Type': 'application/json',
511515
Authorization: `Bearer ${accessToken}`,
512516
Cookie: cookie,

0 commit comments

Comments
 (0)