attributes->get('can_read_sensitive', false) === false) { $team->makeHidden([ 'private_key', ]); } return serializeApiResponse($team); } #[OA\Get( summary: 'List', description: 'List all private keys.', path: '/security/keys', operationId: 'list-private-keys', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], responses: [ new OA\Response( response: 200, description: 'Get all private keys.', content: [ new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'array', items: new OA\Items(ref: '#/components/schemas/PrivateKey') ) ), ]), new OA\Response( response: 401, ref: '#/components/responses/401', ), new OA\Response( response: 400, ref: '#/components/responses/400', ), ] )] public function keys(Request $request) { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } $keys = PrivateKey::where('team_id', $teamId)->get(); return response()->json($this->removeSensitiveData($keys)); } #[OA\Get( summary: 'Get', description: 'Get key by UUID.', path: '/security/keys/{uuid}', operationId: 'get-private-key-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], parameters: [ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( response: 200, description: 'Get all private keys.', content: new OA\JsonContent(ref: '#/components/schemas/PrivateKey') ), new OA\Response( response: 401, ref: '#/components/responses/401', ), new OA\Response( response: 400, ref: '#/components/responses/400', ), new OA\Response( response: 404, description: 'Private Key not found.', ), ] )] public function key_by_uuid(Request $request) { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } $key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first(); if (is_null($key)) { return response()->json([ 'message' => 'Private Key not found.', ], 404); } return response()->json($this->removeSensitiveData($key)); } #[OA\Post( summary: 'Create', description: 'Create a new private key.', path: '/security/keys', operationId: 'create-private-key', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], requestBody: new OA\RequestBody( required: true, content: [ 'application/json' => new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', required: ['private_key'], properties: [ 'name' => ['type' => 'string'], 'description' => ['type' => 'string'], 'private_key' => ['type' => 'string'], ], additionalProperties: false, ) ), ] ), responses: [ new OA\Response( response: 201, description: 'The created private key\'s UUID.', content: [ new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', properties: [ 'uuid' => ['type' => 'string'], ] ) ), ]), new OA\Response( response: 401, ref: '#/components/responses/401', ), new OA\Response( response: 400, ref: '#/components/responses/400', ), ] )] public function create_key(Request $request) { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', 'description' => 'string|max:255', 'private_key' => 'required|string', ]); if ($validator->fails()) { $errors = $validator->errors(); return response()->json([ 'message' => 'Validation failed.', 'errors' => $errors, ], 422); } if (! $request->name) { $request->offsetSet('name', generate_random_name()); } if (! $request->description) { $request->offsetSet('description', 'Created by Coolify via API'); } $isPrivateKeyString = str_starts_with($request->private_key, '-----BEGIN'); if (! $isPrivateKeyString) { try { $base64PrivateKey = base64_decode($request->private_key); $request->offsetSet('private_key', $base64PrivateKey); } catch (\Exception $e) { return response()->json([ 'message' => 'Invalid private key.', ], 422); } } $isPrivateKeyValid = PrivateKey::validatePrivateKey($request->private_key); if (! $isPrivateKeyValid) { return response()->json([ 'message' => 'Invalid private key.', ], 422); } $fingerPrint = PrivateKey::generateFingerprint($request->private_key); $isFingerPrintExists = PrivateKey::fingerprintExists($fingerPrint); if ($isFingerPrintExists) { return response()->json([ 'message' => 'Private key already exists.', ], 422); } $key = PrivateKey::create([ 'team_id' => $teamId, 'name' => $request->name, 'description' => $request->description, 'private_key' => $request->private_key, ]); return response()->json(serializeApiResponse([ 'uuid' => $key->uuid, ]))->setStatusCode(201); } #[OA\Patch( summary: 'Update', description: 'Update a private key.', path: '/security/keys', operationId: 'update-private-key', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], requestBody: new OA\RequestBody( required: true, content: [ 'application/json' => new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', required: ['private_key'], properties: [ 'name' => ['type' => 'string'], 'description' => ['type' => 'string'], 'private_key' => ['type' => 'string'], ], additionalProperties: false, ) ), ] ), responses: [ new OA\Response( response: 201, description: 'The updated private key\'s UUID.', content: [ new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', properties: [ 'uuid' => ['type' => 'string'], ] ) ), ]), new OA\Response( response: 401, ref: '#/components/responses/401', ), new OA\Response( response: 400, ref: '#/components/responses/400', ), ] )] public function update_key(Request $request) { $allowedFields = ['name', 'description', 'private_key']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', 'description' => 'string|max:255', 'private_key' => 'required|string', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { $errors = $validator->errors(); if (! empty($extraFields)) { foreach ($extraFields as $field) { $errors->add($field, 'This field is not allowed.'); } } return response()->json([ 'message' => 'Validation failed.', 'errors' => $errors, ], 422); } $foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first(); if (is_null($foundKey)) { return response()->json([ 'message' => 'Private Key not found.', ], 404); } $foundKey->update($request->all()); return response()->json(serializeApiResponse([ 'uuid' => $foundKey->uuid, ]))->setStatusCode(201); } #[OA\Delete( summary: 'Delete', description: 'Delete a private key.', path: '/security/keys/{uuid}', operationId: 'delete-private-key-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], parameters: [ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( response: 200, description: 'Private Key deleted.', content: [ new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Private Key deleted.'], ] ) ), ]), new OA\Response( response: 401, ref: '#/components/responses/401', ), new OA\Response( response: 400, ref: '#/components/responses/400', ), new OA\Response( response: 404, description: 'Private Key not found.', ), new OA\Response( response: 422, description: 'Private Key is in use and cannot be deleted.', content: [ new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Private Key is in use and cannot be deleted.'], ] ) ), ]), ] )] public function delete_key(Request $request) { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } if (! $request->uuid) { return response()->json(['message' => 'UUID is required.'], 422); } $key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first(); if (is_null($key)) { return response()->json(['message' => 'Private Key not found.'], 404); } if ($key->isInUse()) { return response()->json([ 'message' => 'Private Key is in use and cannot be deleted.', 'details' => 'This private key is currently being used by servers, applications, or Git integrations.', ], 422); } $key->forceDelete(); return response()->json([ 'message' => 'Private Key deleted.', ]); } }