review fixes

This commit is contained in:
x032205
2025-05-12 14:49:45 -04:00
parent d5dbc7d7e0
commit 091e521180
11 changed files with 104 additions and 48 deletions
@@ -17,7 +17,7 @@ export async function up(knex: Knex): Promise<void> {
t.string("type").notNullable(); t.string("type").notNullable();
t.string("tenancyOcid").notNullable(); t.string("tenancyOcid").notNullable();
t.string("allowedUsernames").notNullable(); t.string("allowedUsernames").nullable();
}); });
} }
+1 -1
View File
@@ -18,7 +18,7 @@ export const IdentityOciAuthsSchema = z.object({
identityId: z.string().uuid(), identityId: z.string().uuid(),
type: z.string(), type: z.string(),
tenancyOcid: z.string(), tenancyOcid: z.string(),
allowedUsernames: z.string() allowedUsernames: z.string().nullable().optional()
}); });
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>; export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
@@ -1022,7 +1022,7 @@ interface AddIdentityOciAuthEvent {
metadata: { metadata: {
identityId: string; identityId: string;
tenancyOcid: string; tenancyOcid: string;
allowedUsernames: string; allowedUsernames: string | null;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
@@ -1042,7 +1042,7 @@ interface UpdateIdentityOciAuthEvent {
metadata: { metadata: {
identityId: string; identityId: string;
tenancyOcid?: string; tenancyOcid?: string;
allowedUsernames?: string; allowedUsernames: string | null;
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
@@ -143,7 +143,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
metadata: { metadata: {
identityId: identityOciAuth.identityId, identityId: identityOciAuth.identityId,
tenancyOcid: identityOciAuth.tenancyOcid, tenancyOcid: identityOciAuth.tenancyOcid,
allowedUsernames: identityOciAuth.allowedUsernames, allowedUsernames: identityOciAuth.allowedUsernames || null,
accessTokenTTL: identityOciAuth.accessTokenTTL, accessTokenTTL: identityOciAuth.accessTokenTTL,
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[], accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
@@ -214,7 +214,8 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.body, ...req.body,
identityId: req.params.identityId identityId: req.params.identityId,
allowedUsernames: req.body.allowedUsernames || null
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
@@ -225,7 +226,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
metadata: { metadata: {
identityId: identityOciAuth.identityId, identityId: identityOciAuth.identityId,
tenancyOcid: identityOciAuth.tenancyOcid, tenancyOcid: identityOciAuth.tenancyOcid,
allowedUsernames: identityOciAuth.allowedUsernames, allowedUsernames: identityOciAuth.allowedUsernames || null,
accessTokenTTL: identityOciAuth.accessTokenTTL, accessTokenTTL: identityOciAuth.accessTokenTTL,
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[], accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import RE2 from "re2"; import RE2 from "re2";
@@ -15,6 +16,7 @@ import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -60,15 +62,20 @@ export const identityOciAuthServiceFactory = ({
await blockLocalAndPrivateIpAddresses(headers.host); await blockLocalAndPrivateIpAddresses(headers.host);
// Validate OCI host format // Validate OCI host format
if (!headers.host || !new RE2("^identity\\.[a-zA-Z0-9-]+\\.oraclecloud\\.com$").test(headers.host)) { if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid OCI host format. Expected format: identity.<region>.oraclecloud.com" message: "Invalid OCI host format. Expected format: identity.<region>.oraclecloud.com"
}); });
} }
const { data } = await request.get<TOciGetUserResponse>(`https://${headers.host}/20160918/users/${userOcid}`, { const { data } = await request
headers .get<TOciGetUserResponse>(`https://${headers.host}/20160918/users/${userOcid}`, {
}); headers
})
.catch((err: AxiosError) => {
logger.error(err.response, "OciIdentityLogin: Failed to authenticate with Oracle Cloud");
throw err;
});
if (data.compartmentId !== identityOciAuth.tenancyOcid) { if (data.compartmentId !== identityOciAuth.tenancyOcid) {
throw new UnauthorizedError({ throw new UnauthorizedError({
@@ -13,7 +13,7 @@ export type TLoginOciAuthDTO = {
export type TAttachOciAuthDTO = { export type TAttachOciAuthDTO = {
identityId: string; identityId: string;
tenancyOcid: string; tenancyOcid: string;
allowedUsernames: string; allowedUsernames: string | null;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
@@ -23,8 +23,8 @@ export type TAttachOciAuthDTO = {
export type TUpdateOciAuthDTO = { export type TUpdateOciAuthDTO = {
identityId: string; identityId: string;
tenancyOcid?: string; tenancyOcid: string;
allowedUsernames?: string; allowedUsernames: string | null;
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
@@ -9,16 +9,18 @@ export const validateUsernames = z
.string() .string()
.trim() .trim()
.max(500, "Input exceeds the maximum limit of 500 characters") .max(500, "Input exceeds the maximum limit of 500 characters")
.transform((val) => .nullish()
val .transform((val) => {
if (!val) return [];
return val
.split(",") .split(",")
.map((s) => s.trim()) .map((s) => s.trim())
.filter(Boolean) .filter(Boolean);
) })
.refine((arr) => arr.every((name) => usernameSchema.safeParse(name).success), { .refine((arr) => arr.every((name) => usernameSchema.safeParse(name).success), {
message: "One or more usernames are invalid" message: "One or more usernames are invalid"
}) })
.transform((arr) => arr.join(", ")); .transform((arr) => (arr.length > 0 ? arr.join(", ") : null));
export const validateTenancy = z export const validateTenancy = z
.string() .string()
@@ -43,6 +43,49 @@ To be more specific:
4. Infisical checks the user's properties against set criteria such as **Allowed Usernames** and **Tenancy OCID**. 4. Infisical checks the user's properties against set criteria such as **Allowed Usernames** and **Tenancy OCID**.
5. If all checks pass, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API. 5. If all checks pass, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API.
## Prerequisite
In order to sign requests, you must have an OCI user with credentials such as the private key. If you're unaware of how to create a user and obtain the needed credentials, expand the menu below.
<Accordion title="Creating an OCI user">
<Steps>
<Step title="Search for 'Domains' and click as shown">
![Search Domains](/images/app-connections/oci/search-domains.png)
</Step>
<Step title="Select domain">
Select the domain in which you want to create the Infisical user account.
![Select Domain](/images/app-connections/oci/select-domain.png)
</Step>
<Step title="Navigate to 'Users'">
![Select Users](/images/app-connections/oci/select-users.png)
</Step>
<Step title="Click 'Create user'">
![Click Create User](/images/app-connections/oci/click-create-user.png)
</Step>
<Step title="Create user">
The name, email, and username can be anything.
![Create User](/images/app-connections/oci/create-user.png)
</Step>
<Step title="Navigate to 'API keys'">
After you've created a user, you'll be redirected to the user's page. Navigate to 'API keys'.
![Select API Keys](/images/app-connections/oci/select-api-keys.png)
</Step>
<Step title="Add API key">
Click on 'Add API key' and then download or import the private key. After you've obtained the private key, click 'Add'.
![Add API Key](/images/app-connections/oci/add-api-key.png)
</Step>
<Step title="Store configuration">
After creating the API key, you'll be shown a modal with relevant information. Save the highlighted values (and the private key) for later steps.
![User Info](/images/app-connections/oci/user-info.png)
</Step>
</Steps>
</Accordion>
## Guide ## Guide
In the following steps, we explore how to create and use identities for your workloads and applications on OCI to In the following steps, we explore how to create and use identities for your workloads and applications on OCI to
+3 -3
View File
@@ -294,7 +294,7 @@ export type IdentityOciAuth = {
identityId: string; identityId: string;
type: "iam"; type: "iam";
tenancyOcid: string; tenancyOcid: string;
allowedUsernames: string; allowedUsernames?: string | null;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
@@ -305,7 +305,7 @@ export type AddIdentityOciAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
tenancyOcid: string; tenancyOcid: string;
allowedUsernames: string; allowedUsernames?: string | null;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
@@ -318,7 +318,7 @@ export type UpdateIdentityOciAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
tenancyOcid?: string; tenancyOcid?: string;
allowedUsernames?: string; allowedUsernames?: string | null;
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
@@ -29,8 +29,15 @@ import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
tenancyOcid: z.string().trim().min(1, "Tenancy OCID is required."), tenancyOcid: z
allowedUsernames: z.string(), .string()
.trim()
.min(1, "Tenancy OCID cannot be empty.")
.refine(
(val) => /^ocid1\.tenancy\.oc1\..+$/.test(val),
"Invalid Tenancy OCID format. Must start with ocid1.tenancy.oc1."
),
allowedUsernames: z.string().optional(),
accessTokenTTL: z accessTokenTTL: z
.string() .string()
.refine( .refine(
@@ -110,7 +117,7 @@ export const IdentityOciAuthForm = ({
if (data) { if (data) {
reset({ reset({
tenancyOcid: data.tenancyOcid, tenancyOcid: data.tenancyOcid,
allowedUsernames: data.allowedUsernames, allowedUsernames: data.allowedUsernames || undefined,
accessTokenTTL: String(data.accessTokenTTL), accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
@@ -125,7 +132,7 @@ export const IdentityOciAuthForm = ({
} else { } else {
reset({ reset({
tenancyOcid: "", tenancyOcid: "",
allowedUsernames: "", allowedUsernames: undefined,
accessTokenTTL: "2592000", accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000", accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0", accessTokenNumUsesLimit: "0",
@@ -161,7 +168,7 @@ export const IdentityOciAuthForm = ({
organizationId: orgId, organizationId: orgId,
identityId, identityId,
tenancyOcid, tenancyOcid,
allowedUsernames: allowedUsernames || "", allowedUsernames: allowedUsernames || undefined,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -1,37 +1,33 @@
import { faHome } from '@fortawesome/free-solid-svg-icons' import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
createFileRoute, import { zodValidator } from "@tanstack/zod-adapter";
linkOptions, import { z } from "zod";
stripSearchParams,
} from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
import { SettingsPage } from './SettingsPage' import { SettingsPage } from "./SettingsPage";
const SettingsPageQueryParams = z.object({ const SettingsPageQueryParams = z.object({
selectedTab: z.string().catch(''), selectedTab: z.string().catch("")
}) });
export const Route = createFileRoute( export const Route = createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/organization/settings/', "/_authenticate/_inject-org-details/_org-layout/organization/settings/"
)({ )({
component: SettingsPage, component: SettingsPage,
validateSearch: zodValidator(SettingsPageQueryParams), validateSearch: zodValidator(SettingsPageQueryParams),
search: { search: {
middlewares: [stripSearchParams({ selectedTab: '' })], middlewares: [stripSearchParams({ selectedTab: "" })]
}, },
context: () => ({ context: () => ({
breadcrumbs: [ breadcrumbs: [
{ {
label: 'Home', label: "Home",
icon: () => <FontAwesomeIcon icon={faHome} />, icon: () => <FontAwesomeIcon icon={faHome} />,
link: linkOptions({ to: '/' }), link: linkOptions({ to: "/" })
}, },
{ {
label: 'Settings', label: "Settings"
}, }
], ]
}), })
}) });