mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Work on order
This commit is contained in:
@@ -36,7 +36,7 @@ def step_impl(context: Context, profile_var: str):
|
||||
# TODO: Fixed value for now, just to make test much easier,
|
||||
# we should call infisical API to create such profile instead
|
||||
# in the future
|
||||
profile_id = "0e96a01b-017e-4660-8b3d-ff26018fe0ce"
|
||||
profile_id = "dd6e09c8-d5b8-4bfd-b436-4ab5c93d5d7e"
|
||||
context.vars[profile_var] = AcmeProfile(profile_id)
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { z } from "zod";
|
||||
|
||||
import { AcmeBadPublicKeyError } from "@app/ee/services/pki-acme/pki-acme-errors";
|
||||
import {
|
||||
CreateAcmeAccountBodySchema,
|
||||
CreateAcmeAccountResponseSchema,
|
||||
CreateAcmeOrderBodySchema,
|
||||
CreateAcmeOrderResponseSchema,
|
||||
CreateAcmeOrderSchema,
|
||||
DeactivateAcmeAccountResponseSchema,
|
||||
DeactivateAcmeAccountSchema,
|
||||
DownloadAcmeCertificateSchema,
|
||||
@@ -25,7 +23,6 @@ import {
|
||||
RespondToAcmeChallengeResponseSchema,
|
||||
RespondToAcmeChallengeSchema
|
||||
} from "@app/ee/services/pki-acme/pki-acme-schemas";
|
||||
import { TRawJwsPayload } from "@app/ee/services/pki-acme/pki-acme-types";
|
||||
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
|
||||
@@ -104,29 +101,23 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiAcme],
|
||||
description: "ACME New Account - register a new account or find existing one",
|
||||
...RawJwsPayloadSchema.shape,
|
||||
params: z.object({
|
||||
profileId: z.string().uuid()
|
||||
}),
|
||||
body: RawJwsPayloadSchema,
|
||||
response: {
|
||||
201: CreateAcmeAccountResponseSchema
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const { payload, protectedHeader } = await server.services.pkiAcme.validateJwsPayload(
|
||||
req.body as TRawJwsPayload,
|
||||
async (protectedHeader) => {
|
||||
if (!protectedHeader.jwk) {
|
||||
throw new AcmeBadPublicKeyError({ detail: "JWK is required in the protected header" });
|
||||
}
|
||||
return protectedHeader.jwk as unknown as JsonWebKey;
|
||||
},
|
||||
CreateAcmeAccountBodySchema
|
||||
);
|
||||
const { payload, protectedHeader } = await server.services.pkiAcme.validateNewAccountJwsPayload(req.body);
|
||||
const { alg, jwk } = protectedHeader;
|
||||
const { status, body, headers } = await server.services.pkiAcme.createAcmeAccount(
|
||||
req.params.profileId,
|
||||
const { status, body, headers } = await server.services.pkiAcme.createAcmeAccount({
|
||||
profileId: req.params.profileId,
|
||||
alg,
|
||||
jwk!,
|
||||
jwk: jwk!,
|
||||
payload
|
||||
);
|
||||
});
|
||||
// TODO: DRY
|
||||
res.code(status);
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
@@ -153,7 +144,10 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiAcme],
|
||||
description: "ACME New Order - apply for a new certificate",
|
||||
...CreateAcmeOrderSchema.shape,
|
||||
params: z.object({
|
||||
profileId: z.string().uuid()
|
||||
}),
|
||||
body: RawJwsPayloadSchema,
|
||||
response: {
|
||||
201: CreateAcmeOrderResponseSchema
|
||||
}
|
||||
@@ -161,6 +155,11 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
|
||||
// TODO: replace with verify ACME signature here instead
|
||||
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req, res) => {
|
||||
const { payload, protectedHeader, accountId } = await server.services.pkiAcme.validateExistingAccountJwsPayload(
|
||||
req.params.profileId,
|
||||
req.body,
|
||||
CreateAcmeOrderBodySchema
|
||||
);
|
||||
const order = await server.services.pkiAcme.createAcmeOrder(req.params.profileId, req.body);
|
||||
res.code(201);
|
||||
return order;
|
||||
|
||||
@@ -26,24 +26,9 @@ export const pkiAcmeAccountDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateById = async (id: string, data: TPkiAcmeAccountsUpdate, tx?: Knex) => {
|
||||
const findById = async (profileId: string, id: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAccount).where({ id }).update(data).returning("*");
|
||||
const [account] = result;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return account;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Update PKI ACME account" });
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ id }).first();
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, id }).first();
|
||||
|
||||
return account || null;
|
||||
} catch (error) {
|
||||
@@ -51,16 +36,6 @@ export const pkiAcmeAccountDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findByProfileId = async (profileId: string, tx?: Knex) => {
|
||||
try {
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId }).first();
|
||||
|
||||
return account || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME account by profile id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByPublicKey = async (profileId: string, alg: string, publicKey: unknown, tx?: Knex) => {
|
||||
try {
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, alg, publicKey }).first();
|
||||
@@ -71,35 +46,12 @@ export const pkiAcmeAccountDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findManyByProfileId = async (profileId: string, tx?: Knex) => {
|
||||
try {
|
||||
const accounts = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId });
|
||||
|
||||
return accounts;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find many PKI ACME accounts by profile id" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAccount).where({ id }).delete().returning("*");
|
||||
const [account] = result;
|
||||
|
||||
return account || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete PKI ACME account by id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...pkiAcmeAccountOrm,
|
||||
create,
|
||||
updateById,
|
||||
findById,
|
||||
findByProfileId,
|
||||
findByPublicKey,
|
||||
findManyByProfileId,
|
||||
deleteById
|
||||
findByPublicKey
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { TPkiAcmeOrderDALFactory } from "./pki-acme-order-dal";
|
||||
import { CreateAcmeAccountBodySchema, ProtectedHeaderSchema } from "./pki-acme-schemas";
|
||||
import {
|
||||
TAcmeResponse,
|
||||
TAuthenciatedJwsPayload,
|
||||
TCreateAcmeAccountPayload,
|
||||
TCreateAcmeAccountResponse,
|
||||
TCreateAcmeOrderPayload,
|
||||
@@ -43,7 +44,7 @@ import {
|
||||
|
||||
type TPkiAcmeServiceFactoryDep = {
|
||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
|
||||
acmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findByPublicKey" | "create">;
|
||||
acmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findById" | "findByPublicKey" | "create">;
|
||||
acmeOrderDAL: Pick<TPkiAcmeOrderDALFactory, "create">;
|
||||
};
|
||||
|
||||
@@ -69,6 +70,14 @@ export const pkiAcmeServiceFactory = ({
|
||||
return `${baseUrl}${path}`;
|
||||
};
|
||||
|
||||
const extractAccountIdFromKid = (kid: string, profileId: string): string => {
|
||||
const kidPrefix = buildUrl(`/api/v1/pki/acme/profiles/${profileId}/accounts/`);
|
||||
if (!kid.startsWith(kidPrefix)) {
|
||||
throw new AcmeMalformedError({ detail: "KID must start with the profile account URL" });
|
||||
}
|
||||
return kid.slice(kidPrefix.length);
|
||||
};
|
||||
|
||||
const validateJwsPayload = async <T>(
|
||||
rawJwsPayload: TRawJwsPayload,
|
||||
getJWK: (protectedHeader: JWSHeaderParameters) => Promise<JsonWebKey>,
|
||||
@@ -120,7 +129,7 @@ export const pkiAcmeServiceFactory = ({
|
||||
rawJwsPayload,
|
||||
async (protectedHeader) => {
|
||||
if (!protectedHeader.jwk) {
|
||||
throw new AcmeBadPublicKeyError({ detail: "JWK is required in the protected header" });
|
||||
throw new AcmeMalformedError({ detail: "JWK is required in the protected header" });
|
||||
}
|
||||
return protectedHeader.jwk as unknown as JsonWebKey;
|
||||
},
|
||||
@@ -128,6 +137,36 @@ export const pkiAcmeServiceFactory = ({
|
||||
);
|
||||
};
|
||||
|
||||
const validateExistingAccountJwsPayload = async <T>(
|
||||
profileId: string,
|
||||
rawJwsPayload: TRawJwsPayload,
|
||||
schema: z.ZodSchema<T>
|
||||
): Promise<TAuthenciatedJwsPayload<T>> => {
|
||||
const profile = await validateAcmeProfile(profileId);
|
||||
const result = await validateJwsPayload(
|
||||
rawJwsPayload,
|
||||
async (protectedHeader) => {
|
||||
if (!protectedHeader.kid) {
|
||||
throw new AcmeMalformedError({ detail: "KID is required in the protected header" });
|
||||
}
|
||||
const accountId = extractAccountIdFromKid(protectedHeader.kid, profileId);
|
||||
const account = await acmeAccountDAL.findById(profile.id, accountId);
|
||||
if (!account) {
|
||||
throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" });
|
||||
}
|
||||
if (account.alg !== protectedHeader.alg) {
|
||||
throw new AcmeMalformedError({ detail: "ACME account algorithm mismatch" });
|
||||
}
|
||||
return account.publicKey as JsonWebKey;
|
||||
},
|
||||
schema
|
||||
);
|
||||
return {
|
||||
...result,
|
||||
accountId: extractAccountIdFromKid(result.protectedHeader.kid!, profileId)
|
||||
};
|
||||
};
|
||||
|
||||
const getAcmeDirectory = async (profileId: string): Promise<TGetAcmeDirectoryResponse> => {
|
||||
await validateAcmeProfile(profileId);
|
||||
return {
|
||||
@@ -195,13 +234,25 @@ export const pkiAcmeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const createAcmeOrder = async (
|
||||
profileId: string,
|
||||
account: TPkiAcmeAccounts,
|
||||
payload: TCreateAcmeOrderPayload
|
||||
): Promise<TAcmeResponse<TCreateAcmeOrderResponse>> => {
|
||||
const profile = await validateAcmeProfile(profileId);
|
||||
const createAcmeOrder = async ({
|
||||
profileId,
|
||||
accountId,
|
||||
payload
|
||||
}: {
|
||||
profileId: string;
|
||||
accountId: string;
|
||||
payload: TCreateAcmeOrderPayload;
|
||||
}): Promise<TAcmeResponse<TCreateAcmeOrderResponse>> => {
|
||||
const account = await acmeAccountDAL.findById(profileId, accountId)!;
|
||||
|
||||
// TODO: check and see if we have existing orders for this account that meet the criteria
|
||||
// if we do, return the existing order
|
||||
|
||||
orders = await acmeOrderDAL.create({
|
||||
profileId,
|
||||
accountId,
|
||||
status: "pending"
|
||||
});
|
||||
// FIXME: Implement ACME new order creation
|
||||
const orderId = "FIXME-order-id";
|
||||
return {
|
||||
@@ -315,6 +366,7 @@ export const pkiAcmeServiceFactory = ({
|
||||
return {
|
||||
validateJwsPayload,
|
||||
validateNewAccountJwsPayload,
|
||||
validateExistingAccountJwsPayload,
|
||||
getAcmeDirectory,
|
||||
getAcmeNewNonce,
|
||||
createAcmeAccount,
|
||||
|
||||
@@ -42,6 +42,9 @@ export type TJwsPayload<T> = {
|
||||
protectedHeader: TProtectedHeader;
|
||||
payload: T;
|
||||
};
|
||||
export type TAuthenciatedJwsPayload<T> = TJwsPayload<T> & {
|
||||
accountId: string;
|
||||
};
|
||||
export type TAcmeResponse<TPayload> = {
|
||||
status: number;
|
||||
headers: Record<string, string>;
|
||||
@@ -55,6 +58,11 @@ export type TPkiAcmeServiceFactory = {
|
||||
schema: z.ZodSchema<T>
|
||||
) => Promise<TJwsPayload<T>>;
|
||||
validateNewAccountJwsPayload: (rawJwsPayload: TRawJwsPayload) => Promise<TJwsPayload<TCreateAcmeAccountPayload>>;
|
||||
validateExistingAccountJwsPayload: <T>(
|
||||
profileId: string,
|
||||
rawJwsPayload: TRawJwsPayload,
|
||||
schema: z.ZodSchema<T>
|
||||
) => Promise<TAuthenciatedJwsPayload<T>>;
|
||||
getAcmeDirectory: (profileId: string) => Promise<TGetAcmeDirectoryResponse>;
|
||||
getAcmeNewNonce: (profileId: string) => Promise<string>;
|
||||
createAcmeAccount: ({
|
||||
@@ -68,10 +76,15 @@ export type TPkiAcmeServiceFactory = {
|
||||
jwk: JsonWebKey;
|
||||
payload: TCreateAcmeAccountPayload;
|
||||
}) => Promise<TAcmeResponse<TCreateAcmeAccountResponse>>;
|
||||
createAcmeOrder: (
|
||||
profileId: string,
|
||||
body: TCreateAcmeOrderPayload
|
||||
) => Promise<TAcmeResponse<TCreateAcmeOrderResponse>>;
|
||||
createAcmeOrder: ({
|
||||
profileId,
|
||||
accountId,
|
||||
payload
|
||||
}: {
|
||||
profileId: string;
|
||||
accountId: string;
|
||||
payload: TCreateAcmeOrderPayload;
|
||||
}) => Promise<TAcmeResponse<TCreateAcmeOrderResponse>>;
|
||||
deactivateAcmeAccount: (
|
||||
profileId: string,
|
||||
accountId: string,
|
||||
|
||||
Reference in New Issue
Block a user