Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
46 changes: 43 additions & 3 deletions app/common/src/services/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as newtype from '../utilities/data/newtype.js'
import * as permissions from '../utilities/permissions.js'
import { getFileDetailsPath } from './Backend/remoteBackendPaths.js'
import {
ApiKeyId,
DatalinkId,
DirectoryId,
EnsoPath,
Expand Down Expand Up @@ -590,6 +591,11 @@ export interface RemoteBackendError {
readonly param: string
}

/** HTTP response body for the "list api keys" endpoint. */
export interface ListApiKeysResponse {
readonly credentials: readonly ApiKey[]
}

/** HTTP response body for the "list users" endpoint. */
export interface ListUsersResponseBody {
readonly users: readonly User[]
Expand Down Expand Up @@ -793,6 +799,35 @@ export interface LChColor {
readonly alpha?: number | undefined
}

/** Type used when creating api key credential. */
export interface CreateApiKeyRequestBody {
readonly name: string
readonly description: string
readonly expiresIn: ApiKeyExpiresIn
}

/** Api key credential. */
export interface ApiKey {
readonly id: ApiKeyId
// Field populated only once after creation.
readonly secretId: string | null
readonly name: string
readonly description: string
readonly createdAt: dateTime.Rfc3339DateTime
readonly lastUsedAt: dateTime.Rfc3339DateTime | null
readonly expiresAt: dateTime.Rfc3339DateTime | null
}

/** Possible types of lifetime span for api key credentials. */
export enum ApiKeyExpiresIn {
Week = 'Week',
Month = 'Month',
Year = 'Year',
Indefinetly = 'Indefinetly',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PabloBuchu Indefinitely is the correct spelling (but maybe Indefinite is more appropriate, given that the others aren't Yearly (for example))

}

export const API_KEY_EXPIRES_IN_VALUES: readonly ApiKeyExpiresIn[] = Object.values(ApiKeyExpiresIn)

/** A pre-selected list of colors to be used in color pickers. */
export const COLORS = [
// Red
Expand Down Expand Up @@ -1167,9 +1202,7 @@ export interface UpdateProjectRequestBody {
readonly projectName: string | null
}

/**
* Extra parameters required when opening the project in hybrid mode.
*/
/** Extra parameters required when opening the project in hybrid mode. */
export interface OpenHybridProjectParameters {
/** Cloud project directory path. */
readonly cloudProjectDirectoryPath: EnsoPath
Expand Down Expand Up @@ -1956,6 +1989,13 @@ export default abstract class Backend {
/** Fetches pricing page configuration. */
abstract getPaymentsConfig(): Promise<PaymentsConfig>

/** List all API keys for the current user. */
abstract listApiKeys(): Promise<readonly ApiKey[]>
/** Create a new API key for the current user. */
abstract createApiKey(body: CreateApiKeyRequestBody): Promise<ApiKey>
/** Delete a API key for the current user. */
abstract deleteApiKey(apiKeyId: ApiKeyId): Promise<void>

/** Throw a {@link backend.NotAuthorizedError} if the response is a 401 Not Authorized status code. */
private async checkForAuthenticationError<T>(
makeRequest: () => Promise<ResponseWithTypedJson<T>>,
Expand Down
11 changes: 10 additions & 1 deletion app/common/src/services/Backend/remoteBackendPaths.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @file Paths used by the `RemoteBackend`. */
import {
ApiKeyId,
DirectoryId,
HttpsUrl,
type AssetId,
Expand Down Expand Up @@ -88,10 +89,18 @@ export const GET_LOG_EVENTS_PATH = 'log_events'
export const POST_LOG_EVENT_PATH = 'logs'
/** Relative HTTP path to the "get payments config" endpoint of the Cloud backend API. */
export const PAYMENTS_CONFIG_PATH = 'payments/config'
/** Resolve an enso URL path. */
/** Relative HTTP path to the "resolve an enso URL path" endpoint of the Cloud backend API. */
export const RESOLVE_ENSO_PATH = 'path/resolve'
/** Relative HTTP path to the "get customer portal session" endpoint of the Cloud backend API. */
export const CUSTOMER_PORTAL_SESSION_CREATE_PATH = 'payments/customer-portal-sessions/create'
/** Relative HTTP path to the "API keys" endpoint of the Cloud backend API. */
export const LIST_API_KEYS_PATH = 'credentials'
/** Relative HTTP path to the "create API key" endpoint of the Cloud backend API. */
export const CREATE_API_KEY_PATH = 'credentials'
/** Relative HTTP path to the "delete API key" endpoint of the Cloud backend API. */
export function deleteApiKeyPath(apiKeyId: ApiKeyId) {
return `credentials/${apiKeyId}`
}

/** Relative HTTP path to the "cancel subscription" endpoint of the Cloud backend API. */
export function cancelSubscriptionPath(subscriptionId: SubscriptionId) {
Expand Down
4 changes: 4 additions & 0 deletions app/common/src/services/Backend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ export const ZipAssetsJobId = newtypeConstructor<ZipAssetsJobId>()
export type UnzipAssetsJobId = Newtype<string, 'UnzipAssetsJobId'>
export const UnzipAssetsJobId = newtypeConstructor<UnzipAssetsJobId>()

/** Unique identifier for an API key. */
export type ApiKeyId = Newtype<string, 'ApiKeyId'>
export const ApiKeyId = newtypeConstructor<ApiKeyId>()

/** The name of an asset label. */
export type LabelName = Newtype<string, 'LabelName'>
export const LabelName = newtypeConstructor<LabelName>()
Expand Down
3 changes: 3 additions & 0 deletions app/common/src/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ interface PlaceholderOverrides {

readonly welcomeToTeam: [organizationName: string]
readonly invitationText: [organizationName: string]

readonly youCanCreateXMoreApiKeys: [apiKeysLeft: number]
readonly deleteApiKeyConfirmation: [tokenName: string]
}

// This is intentionally unused. This line throws an error if `PlaceholderOverrides` ever becomes
Expand Down
28 changes: 24 additions & 4 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
"changeUserGroupsError": "Could not set user groups",
"deleteUserGroupError": "Could not delete user group '$0'",
"deleteUserError": "Could not delete user '$0'",
"deleteUserConfirmation": "permanently delete user: '$0 ($1)'",
"deleteUserConfirmation": "permanently delete the user '$0 ($1)'",
"deleteUserAlert": "We will transfer all belonging assets to the organization admin account.",
"deleteApiKeyConfirmation": "delete the API key '$0'",
"anotherProjectIsBeingOpenedError": "Another project is currently being opened",
"syncingProjectFiles": "Synchronizing Project Files",
"localBackendNotDetectedError": "Could not detect the local backend",
Expand Down Expand Up @@ -183,6 +184,7 @@
"logEventBackendError": "Could not log an event '$0'",
"getDefaultVersionBackendError": "No default $0 version found",
"duplicateUserGroupError": "This user group already exists",
"duplicateApiKeyError": "An API key with this name already exists.",
"getCustomerPortalUrlBackendError": "Could not get customer portal URL",
"exportArchiveBackendError": "Could not export archive",
"duplicateLabelError": "This label already exists.",
Expand Down Expand Up @@ -596,6 +598,7 @@
"organizationInviteErrorMessage": "Something went wrong. Please contact the administrators at",
"youHaveNoUserGroupsAdmin": "This organization has no user groups. You can create one using the button above.",
"youHaveNoUserGroupsNonAdmin": "This organization has no user groups. You can create one using the button above.",
"youHaveNoApiKeys": "You have no API keys. You can create one using the button above.",
"xIsUsingTheProject": "'$0' is currently using the project",
"uploadLargeFileStatus": "Uploading file... ($0/$1MB)",
"uploadLargeFileSuccess": "Finished uploading file.",
Expand Down Expand Up @@ -925,6 +928,12 @@
"userGroupsPaywallMessage": "You have reached the limit of user groups for your plan. Upgrade to create more user groups.",
"userGroupsLimitMessage": "You can create up to $0 user group(s)",
"userGroupNamePlaceholder": "Enter the name of the user group",
"apiKeys": "API Keys",
"newApiKey": "New API Key",
"expiresIn": "Expiration",
"createdAt": "Created at",
"lastUsedAt": "Last used at",
"never": "Never",
"assetSearchFieldLabel": "Search through items",
"startModalLabel": "Start modal",
"userMenuLabel": "User Settings",
Expand Down Expand Up @@ -1039,6 +1048,9 @@
"securitySettingsTabSection": "Security",
"activityLogSettingsTab": "Activity log",
"activityLogSettingsSection": "Activity Log",
"apiKeysSettingsTab": "API Keys",
"apiKeysSettingsSection": "API Keys",
"apiKeysSettingsCustomEntryAliases": "api keys\napi key",
"free": "Community",
"freePlan": "Community Plan",
"solo": "Solo",
Expand Down Expand Up @@ -1250,14 +1262,22 @@
"downgradedTitle": "Cloud Assets Removal",
"downgradedExplanation": "You have recently downgraded your plan to Community, which means you can no longer store assets in cloud.",
"downgradedWarning": "We will permanently remove all of your cloud assets in $0 days and $1 hours.",
"cancelSubscriptionBackendError": "We couldn't cancel your subscription. Please contant the administrator.",
"cancelSubscriptionBackendError": "Could not cancel your subscription. Please contact an administrator.",
"downgrade": "Downgrade",
"getPaymentsConfigBackendError": "Couldn't get payment pricing page configuration. Please try again later.",
"getPaymentsConfigBackendError": "Could not get payment pricing page configuration.",
"listApiKeysBackendError": "Could not list API keys.",
"createApiKeyBackendError": "Could not create API key.",
"deleteApiKeyBackendError": "Could not delete API key.",
"keyId": "Access key",
"secretId": "Secret access key",
"accessKeyAlert": "If you lose or forget your secret access key, you cannot retrieve it. Instead, create a new access key and make the old key inactive.",
"youCanCreateXMoreApiKeys": "You can create $0 more API key(s).",
"youHaveTheMaximumNumberOfApiKeys": "You have the maximum number of API keys. Delete existing keys to create new ones.",
"stripeRedirectAlert": "You are being redirected to Stripe for finalizing your payment process.",
"stripeRedirectInfo": "After submitting your order you will be redirect to Stripe for finalizing your payment process.",
"goToStripe": "Go to Stripe",
"welcomeToTeam": "Welcome to the \"$0\" team!",
"invitationError": "Something went wrong. Please contact the administrator.",
"invitationError": "Something went wrong when inviting a user. Please contact an administrator.",
"pendingInvitationInfo": "You have pending team invitation.",
"invitationText": "\"$0\" invites you to join",
"invitationAlert": "All of your assets will be transfered with you. This might take a while.",
Expand Down
3 changes: 1 addition & 2 deletions app/gui/src/dashboard/hooks/projectHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ export function useOpenProjectMutation() {
type,
hybrid,
inBackground = false,
suppressHybridProjectOpen: _ = false,
ensoPath,
}: LaunchedProject & { inBackground?: boolean; suppressHybridProjectOpen?: boolean }) => {
assert(() => !closingProjects.has(id))
Expand Down Expand Up @@ -614,7 +613,7 @@ export function useReopenProject(openProjectMutation: ReturnType<typeof useOpenP
const { remoteBackend } = useBackends()

return eventCallbacks.useEventCallback(
async (project: LaunchedProject & { readonly suppressHybridProjectOpen?: boolean }) => {
async (project: LaunchedProject & { suppressHybridProjectOpen?: boolean }) => {
if (project.hybrid && project.suppressHybridProjectOpen !== true) {
await remoteBackend.setHybridOpenInProgress(project.hybrid.cloudProjectId, project.title)
}
Expand Down
Loading
Loading