Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Designed with developer experience in mind, the CLI makes it easy to integrate *
- 🏗️ Automate policy operations in CI/CD with IaC and GitOps
- ✨ Generate policies from natural language using AI
- 🔐 Manage users, roles, and permissions directly from your terminal
- 🌍 Multi-region support for US and EU deployments

> :bulb: The CLI is fully open source and is built with Pastel, using TypeScript and a React-style architecture. Contributions welcome!

Expand Down Expand Up @@ -146,13 +147,46 @@ The `login` command will take you to the browser to perform user authentication

- `--api-key <string>` - store a Permit API key in your workstation keychain instead of running browser authentication
- `--workspace <string>` - predefined workspace key to skip the workspace selection step
- `--region <us | eu>` - specify the Permit region to use (`default: us`). The region determines which Permit.io API endpoints the CLI will communicate with.

**Example:**
**Examples:**

Login with default US region:

```bash
$ permit login
```

Login with EU region:

```bash
$ permit login --region eu
```

Login with API key and EU region:

```bash
$ permit login --api-key permit_key_abc123 --region eu
```

**Region Support:**

Permit.io operates in multiple regions. When you log in with a specific region, the CLI will:
- Store your region preference in your system keychain
- Use the appropriate regional endpoints for all subsequent commands
- Generate Terraform configurations with the correct regional API URLs

Available regions:
- `us` (default) - United States region (`https://api.permit.io`)
- `eu` - European Union region (`https://api.eu.permit.io`)

You can also set the region using the `PERMIT_REGION` environment variable:

```bash
export PERMIT_REGION=eu
permit login
```

---

#### `permit logout`
Expand Down Expand Up @@ -412,6 +446,27 @@ Print out the output to the console -
$ permit env export terraform
```

**Region Support:**

The generated Terraform configuration will automatically use the correct API URL based on your configured region:

- **US region**: `api_url = "https://api.permit.io"`
- **EU region**: `api_url = "https://api.eu.permit.io"`

The region is determined by:
1. The `PERMIT_REGION` environment variable (if set)
2. The region stored from your last `permit login --region <region>` command
3. Defaults to `us` if no region is specified

Example for EU region:

```bash
$ export PERMIT_REGION=eu
$ permit env export terraform --file permit-eu-config.tf
```

This ensures that when you run `terraform apply`, the Terraform provider will communicate with the correct regional Permit.io API.

## Fine-Grained Authorization Configuration

Use natural language commands with AI to instantly set up and enforce fine-grained authorization policies.
Expand Down
3 changes: 2 additions & 1 deletion source/commands/env/export/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WarningCollector } from './types.js';
import { getPermitApiUrl } from '../../../config.js';

export function createSafeId(...parts: string[]): string {
return parts
Expand Down Expand Up @@ -36,7 +37,7 @@ variable "PERMIT_API_KEY" {
}

provider "permitio" {
api_url = "https://api.permit.io"
api_url = "${getPermitApiUrl()}"
api_key = var.PERMIT_API_KEY
}
`;
Expand Down
25 changes: 23 additions & 2 deletions source/commands/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React, { useCallback, useEffect, useState } from 'react';
import { Text } from 'ink';
import { type infer as zInfer, object, string } from 'zod';
import { option } from 'pastel';
import { saveAuthToken } from '../lib/auth.js';
import { saveAuthToken, saveRegion } from '../lib/auth.js';
import { setRegion } from '../config.js';
import LoginFlow from '../components/LoginFlow.js';
import EnvironmentSelection, {
ActiveState,
Expand All @@ -25,6 +26,14 @@ export const options = object({
description: 'Use predefined workspace to Login',
}),
),
region: string()
.optional()
.describe(
option({
description: 'Permit region: us or eu (default: us)',
alias: 'r',
}),
),
});

type Props = {
Expand All @@ -38,9 +47,14 @@ type Props = {
};

export default function Login({
options: { apiKey, workspace },
options: { apiKey, workspace, region },
loginSuccess,
}: Props) {
// Set region IMMEDIATELY before anything else (synchronously)
if (region && (region === 'us' || region === 'eu')) {
Comment thread
EliMoshkovich marked this conversation as resolved.
setRegion(region as 'us' | 'eu');
}

const [state, setState] = useState<'login' | 'signup' | 'env' | 'done'>(
'login',
);
Expand All @@ -51,6 +65,13 @@ export default function Login({
const [organization, setOrganization] = useState<string>('');
const [environment, setEnvironment] = useState<string>('');

// Save region to keystore after successful login
useEffect(() => {
if (region && (region === 'us' || region === 'eu')) {
saveRegion(region as 'us' | 'eu');
}
}, [region]);

const onEnvironmentSelectSuccess = useCallback(
async (
organisation: ActiveState,
Expand Down
7 changes: 6 additions & 1 deletion source/components/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import React, {
useState,
} from 'react';
import { Text, Newline } from 'ink';
import { loadAuthToken } from '../lib/auth.js';
import { loadAuthToken, loadRegion } from '../lib/auth.js';
import Login from '../commands/login.js';
import {
ApiKeyCreate,
Expand Down Expand Up @@ -125,6 +125,11 @@ export function AuthProvider({

// Step: 1, This useEffect is the heart of AuthProvider, it decides which flow to choose based on the props passed.
useEffect(() => {
// Load region from storage on initialization
loadRegion().catch(() => {
// Ignore errors - will default to 'us'
});

// Loads the token stored on our system if any, if no token is found or if the scope of the token is not right,
// we redirect user to login.
const fetchAuthToken = async (
Expand Down
3 changes: 2 additions & 1 deletion source/components/policy/create/TerraformGenerator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PolicyData } from './types.js';
import { getPermitApiUrl } from '../../../config.js';

interface TerraformGeneratorProps {
tableData: PolicyData;
Expand Down Expand Up @@ -37,7 +38,7 @@ export const generateTerraform = ({
}

provider "permitio" {
api_url = "https://api.permit.io"
api_url = "${getPermitApiUrl()}"
api_key = "${authToken}"
}

Expand Down
90 changes: 80 additions & 10 deletions source/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,90 @@
export const KEY_FILE_PATH = './permit.key';
export const KEYSTORE_PERMIT_SERVICE_NAME = 'Permit.io';
export const DEFAULT_PERMIT_KEYSTORE_ACCOUNT = 'PERMIT_DEFAULT_ENV';
export const CLOUD_PDP_URL = 'https://cloudpdp.api.permit.io';
export const PERMIT_API_URL = 'https://api.permit.io';
export const PERMIT_API_STATISTICS_URL =
'https://pdp-statistics.api.permit.io/v2/stats';
export const API_URL = 'https://api.permit.io/v2/';
export const FACTS_API_URL = `${API_URL}facts/`;
export const API_PDPS_CONFIG_URL = `${API_URL}pdps/me/config`;
export const PERMIT_ORIGIN_URL = 'https://app.permit.io';
export const REGION_KEYSTORE_ACCOUNT = 'PERMIT_REGION';

// Region type
export type PermitRegion = 'us' | 'eu';

// Get region from environment variable or default to 'us'
let currentRegion: PermitRegion = (process.env['PERMIT_REGION'] as PermitRegion) || 'us';

// Function to set the current region
export const setRegion = (region: PermitRegion) => {
currentRegion = region;
};

// Function to get the current region
export const getRegion = (): PermitRegion => {
return currentRegion;
};

// Function to get region-specific subdomain
const getRegionSubdomain = (region: PermitRegion): string => {
return region === 'eu' ? 'eu.' : '';
};

// Region-aware URL getters
export const getPermitApiUrl = (): string => {
const subdomain = getRegionSubdomain(currentRegion);
return `https://api.${subdomain}permit.io`;
};

export const getPermitOriginUrl = (): string => {
const subdomain = getRegionSubdomain(currentRegion);
return `https://app.${subdomain}permit.io`;
};

export const getAuthPermitDomain = (): string => {
const subdomain = getRegionSubdomain(currentRegion);
return `app.${subdomain}permit.io`;
};

export const getCloudPdpUrl = (): string => {
if (currentRegion === 'eu') {
return 'https://cloudpdp.api.eu-central-1.permit.io';
}
return 'https://cloudpdp.api.permit.io';
};

export const getPermitApiStatisticsUrl = (): string => {
if (currentRegion === 'eu') {
return 'https://pdp-statistics.api.eu-central-1.permit.io/v2/stats';
}
return 'https://pdp-statistics.api.permit.io/v2/stats';
};

export const getApiUrl = (): string => {
return `${getPermitApiUrl()}/v2/`;
};

export const getFactsApiUrl = (): string => {
return `${getApiUrl()}facts/`;
};

export const getApiPdpsConfigUrl = (): string => {
return `${getApiUrl()}pdps/me/config`;
};

export const getAuthApiUrl = (): string => {
return `${getPermitApiUrl()}/v1/`;
};

// Legacy exports (maintain backwards compatibility)
export const CLOUD_PDP_URL = getCloudPdpUrl();
export const PERMIT_API_URL = getPermitApiUrl();
export const PERMIT_API_STATISTICS_URL = getPermitApiStatisticsUrl();
export const API_URL = getApiUrl();
export const FACTS_API_URL = getFactsApiUrl();
export const API_PDPS_CONFIG_URL = getApiPdpsConfigUrl();
export const PERMIT_ORIGIN_URL = getPermitOriginUrl();
export const AUTH_PERMIT_DOMAIN = getAuthPermitDomain();
export const AUTH_API_URL = getAuthApiUrl();

export const AUTH_REDIRECT_HOST = 'localhost';
export const AUTH_REDIRECT_PORT = 62419;
export const AUTH_REDIRECT_URI = `http://${AUTH_REDIRECT_HOST}:${AUTH_REDIRECT_PORT}`;
export const AUTH_PERMIT_DOMAIN = 'app.permit.io';
export const AUTH_API_URL = 'https://api.permit.io/v1/';
// auth.permit.io is common for both regions
export const AUTH_PERMIT_URL = 'https://auth.permit.io';

export const TERRAFORM_PERMIT_URL =
Expand Down
12 changes: 6 additions & 6 deletions source/hooks/useClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { paths } from '../lib/api/v1.js';
import { CLOUD_PDP_URL, PERMIT_API_URL, PERMIT_ORIGIN_URL } from '../config.js';
import { getCloudPdpUrl, getPermitApiUrl, getPermitOriginUrl } from '../config.js';
import type { paths as PdpPaths } from '../lib/api/pdp-v1.js';

import createClient, {
Expand Down Expand Up @@ -308,10 +308,10 @@ const useClient = () => {

const authenticatedApiClient = useCallback(() => {
const client = createClient<paths>({
baseUrl: PERMIT_API_URL,
baseUrl: getPermitApiUrl(),
headers: {
Accept: '*/*',
Origin: PERMIT_ORIGIN_URL,
Origin: getPermitOriginUrl(),
'Content-Type': 'application/json',
Authorization: `Bearer ${globalTokenGetterSetter.tokenGetter()}`,
},
Expand All @@ -321,7 +321,7 @@ const useClient = () => {

const authenticatedPdpClient = useCallback((pdp_url?: string) => {
const client = createClient<PdpPaths>({
baseUrl: pdp_url ?? CLOUD_PDP_URL,
baseUrl: pdp_url ?? getCloudPdpUrl(),
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
Expand Down Expand Up @@ -503,10 +503,10 @@ const useClient = () => {
cookie?: string | null,
) => {
const client = createClient<paths>({
baseUrl: PERMIT_API_URL,
baseUrl: getPermitApiUrl(),
headers: {
Accept: '*/*',
Origin: PERMIT_ORIGIN_URL,
Origin: getPermitOriginUrl(),
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
Cookie: cookie,
Expand Down
6 changes: 3 additions & 3 deletions source/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PERMIT_API_URL, PERMIT_ORIGIN_URL } from '../config.js';
import { getPermitApiUrl, getPermitOriginUrl } from '../config.js';

type ApiResponse<T> = {
headers: Headers;
Expand Down Expand Up @@ -26,7 +26,7 @@ export const apiCall = async <T = any>(
method,
headers: {
Accept: '*/*',
Origin: PERMIT_ORIGIN_URL,
Origin: getPermitOriginUrl(),
Authorization: `Bearer ${token}`,
Cookie: cookie ?? '',
'Content-Type': 'application/json',
Expand All @@ -38,7 +38,7 @@ export const apiCall = async <T = any>(
}

try {
const res = await fetch(`${PERMIT_API_URL}/${endpoint}`, options);
const res = await fetch(`${getPermitApiUrl()}/${endpoint}`, options);

if (!res.ok) {
const errorText = await res.json();
Expand Down
Loading
Loading