2025-05-15 19:49:42 -07:00
import structlog
2025-10-08 14:39:15 -06:00
from fastapi import BackgroundTasks , Body , Depends , HTTPException , Path , Query
2025-05-15 19:49:42 -07:00
2025-10-10 10:10:18 -06:00
from skyvern . config import settings
2025-05-15 19:49:42 -07:00
from skyvern . forge import app
2025-08-05 07:34:26 -07:00
from skyvern . forge . sdk . db . enums import OrganizationAuthTokenType
2025-05-18 13:45:46 -07:00
from skyvern . forge . sdk . routes . code_samples import (
2025-10-28 17:43:19 -06:00
CREATE_CREDENTIAL_CODE_SAMPLE_CREDIT_CARD_PYTHON ,
CREATE_CREDENTIAL_CODE_SAMPLE_CREDIT_CARD_TS ,
CREATE_CREDENTIAL_CODE_SAMPLE_PYTHON ,
CREATE_CREDENTIAL_CODE_SAMPLE_TS ,
DELETE_CREDENTIAL_CODE_SAMPLE_PYTHON ,
DELETE_CREDENTIAL_CODE_SAMPLE_TS ,
GET_CREDENTIAL_CODE_SAMPLE_PYTHON ,
GET_CREDENTIAL_CODE_SAMPLE_TS ,
GET_CREDENTIALS_CODE_SAMPLE_PYTHON ,
GET_CREDENTIALS_CODE_SAMPLE_TS ,
SEND_TOTP_CODE_CODE_SAMPLE_PYTHON ,
SEND_TOTP_CODE_CODE_SAMPLE_TS ,
2025-05-18 13:45:46 -07:00
)
2025-05-15 19:49:42 -07:00
from skyvern . forge . sdk . routes . routers import base_router , legacy_base_router
from skyvern . forge . sdk . schemas . credentials import (
CreateCredentialRequest ,
2025-11-04 10:29:51 -07:00
Credential ,
2025-05-15 19:49:42 -07:00
CredentialResponse ,
CredentialType ,
2025-10-10 10:10:18 -06:00
CredentialVaultType ,
2025-05-15 19:49:42 -07:00
CreditCardCredentialResponse ,
PasswordCredentialResponse ,
)
2025-08-05 07:34:26 -07:00
from skyvern . forge . sdk . schemas . organizations import (
2025-09-23 10:16:48 -06:00
AzureClientSecretCredentialResponse ,
CreateAzureClientSecretCredentialRequest ,
2025-08-05 07:34:26 -07:00
CreateOnePasswordTokenRequest ,
CreateOnePasswordTokenResponse ,
Organization ,
)
2025-10-14 16:24:14 +08:00
from skyvern . forge . sdk . schemas . totp_codes import OTPType , TOTPCode , TOTPCodeCreate
2025-05-15 19:49:42 -07:00
from skyvern . forge . sdk . services import org_auth_service
from skyvern . forge . sdk . services . bitwarden import BitwardenService
2025-10-10 10:10:18 -06:00
from skyvern . forge . sdk . services . credential . credential_vault_service import CredentialVaultService
2025-10-14 16:24:14 +08:00
from skyvern . services . otp_service import OTPValue , parse_otp_login
2025-05-15 19:49:42 -07:00
LOG = structlog . get_logger ( )
2025-10-08 14:39:15 -06:00
async def fetch_credential_item_background ( item_id : str ) - > None :
"""
Background task to fetch the recently added credential item from Bitwarden .
This triggers Bitwarden to sync the vault earlier so the next request does not have to wait for the sync .
"""
try :
LOG . info ( " Pre-fetching credential item from Bitwarden in background " , item_id = item_id )
credential_item = await BitwardenService . get_credential_item ( item_id )
LOG . info ( " Successfully fetched credential item from Bitwarden " , item_id = item_id , name = credential_item . name )
except Exception as e :
LOG . exception ( " Failed to fetch credential item from Bitwarden in background " , item_id = item_id , error = str ( e ) )
2025-07-15 02:01:23 -07:00
@legacy_base_router.post ( " /totp " )
2025-05-15 19:49:42 -07:00
@legacy_base_router.post ( " /totp/ " , include_in_schema = False )
@base_router.post (
" /credentials/totp " ,
response_model = TOTPCode ,
2025-07-15 02:05:33 -07:00
summary = " Send TOTP code " ,
2025-05-21 20:06:34 -07:00
description = " Forward a TOTP (2FA, MFA) email or sms message containing the code to Skyvern. This endpoint stores the code in database so that Skyvern can use it while running tasks/workflows. " ,
2025-05-15 19:49:42 -07:00
tags = [ " Credentials " ] ,
openapi_extra = {
" x-fern-sdk-method-name " : " send_totp_code " ,
2025-10-28 17:43:19 -06:00
" x-fern-examples " : [
{
" code-samples " : [
{ " sdk " : " python " , " code " : SEND_TOTP_CODE_CODE_SAMPLE_PYTHON } ,
{ " sdk " : " typescript " , " code " : SEND_TOTP_CODE_CODE_SAMPLE_TS } ,
]
}
] ,
2025-05-15 19:49:42 -07:00
} ,
)
2025-05-18 13:45:46 -07:00
@base_router.post (
" /credentials/totp/ " ,
response_model = TOTPCode ,
include_in_schema = False ,
)
2025-05-15 19:49:42 -07:00
async def send_totp_code (
2025-09-11 18:10:05 -07:00
data : TOTPCodeCreate ,
curr_org : Organization = Depends ( org_auth_service . get_current_org ) ,
2025-05-15 19:49:42 -07:00
) - > TOTPCode :
LOG . info (
2025-10-15 01:28:42 +08:00
" Saving OTP code " ,
2025-05-15 19:49:42 -07:00
organization_id = curr_org . organization_id ,
totp_identifier = data . totp_identifier ,
task_id = data . task_id ,
workflow_id = data . workflow_id ,
2025-07-17 21:36:18 -07:00
workflow_run_id = data . workflow_run_id ,
2025-05-15 19:49:42 -07:00
)
2025-11-16 07:10:49 -08:00
# validate task_id, workflow_id, workflow_run_id are valid ids in db if provided
if data . task_id :
task = await app . DATABASE . get_task ( data . task_id , curr_org . organization_id )
if not task :
raise HTTPException ( status_code = 400 , detail = f " Invalid task id: { data . task_id } " )
if data . workflow_id :
workflow = await app . DATABASE . get_workflow ( data . workflow_id , curr_org . organization_id )
if not workflow :
raise HTTPException ( status_code = 400 , detail = f " Invalid workflow id: { data . workflow_id } " )
if data . workflow_run_id :
workflow_run = await app . DATABASE . get_workflow_run ( data . workflow_run_id , curr_org . organization_id )
if not workflow_run :
raise HTTPException ( status_code = 400 , detail = f " Invalid workflow run id: { data . workflow_run_id } " )
2025-05-26 13:28:20 -07:00
content = data . content . strip ( )
2025-10-15 01:28:42 +08:00
otp_value : OTPValue | None = OTPValue ( value = content , type = OTPType . TOTP )
2025-05-26 13:28:20 -07:00
# We assume the user is sending the code directly when the length of code is less than or equal to 10
if len ( content ) > 10 :
2025-10-15 01:28:42 +08:00
otp_value = await parse_otp_login ( content , curr_org . organization_id )
if not otp_value :
2025-05-26 13:28:20 -07:00
LOG . error (
2025-10-15 01:28:42 +08:00
" Failed to parse otp login " ,
2025-05-26 13:28:20 -07:00
totp_identifier = data . totp_identifier ,
task_id = data . task_id ,
workflow_id = data . workflow_id ,
workflow_run_id = data . workflow_run_id ,
content = data . content ,
)
2025-10-15 01:28:42 +08:00
raise HTTPException ( status_code = 400 , detail = " Failed to parse otp login " )
2025-10-14 16:24:14 +08:00
return await app . DATABASE . create_otp_code (
2025-05-15 19:49:42 -07:00
organization_id = curr_org . organization_id ,
totp_identifier = data . totp_identifier ,
content = data . content ,
2025-10-15 01:28:42 +08:00
code = otp_value . value ,
2025-05-15 19:49:42 -07:00
task_id = data . task_id ,
workflow_id = data . workflow_id ,
workflow_run_id = data . workflow_run_id ,
source = data . source ,
expired_at = data . expired_at ,
2025-10-15 01:28:42 +08:00
otp_type = otp_value . get_otp_type ( ) ,
2025-05-15 19:49:42 -07:00
)
2025-10-29 20:49:25 -07:00
@base_router.get (
" /credentials/totp " ,
response_model = list [ TOTPCode ] ,
summary = " List TOTP codes " ,
description = " Retrieves recent TOTP codes for the current organization. " ,
tags = [ " Credentials " ] ,
openapi_extra = {
" x-fern-sdk-method-name " : " get_totp_codes " ,
} ,
include_in_schema = False ,
)
@base_router.get (
" /credentials/totp/ " ,
response_model = list [ TOTPCode ] ,
include_in_schema = False ,
)
async def get_totp_codes (
curr_org : Organization = Depends ( org_auth_service . get_current_org ) ,
totp_identifier : str | None = Query (
None ,
description = " Filter by TOTP identifier such as an email or phone number. " ,
examples = [ " john.doe@example.com " ] ,
) ,
workflow_run_id : str | None = Query (
None ,
description = " Filter by workflow run ID. " ,
examples = [ " wr_123456 " ] ,
) ,
otp_type : OTPType | None = Query (
None ,
description = " Filter by OTP type (e.g. totp, magic_link). " ,
examples = [ OTPType . TOTP . value ] ,
) ,
limit : int = Query (
50 ,
ge = 1 ,
le = 200 ,
description = " Maximum number of codes to return. " ,
) ,
) - > list [ TOTPCode ] :
if totp_identifier :
codes = await app . DATABASE . get_otp_codes (
organization_id = curr_org . organization_id ,
totp_identifier = totp_identifier ,
otp_type = otp_type ,
workflow_run_id = workflow_run_id ,
limit = limit ,
)
else :
codes = await app . DATABASE . get_recent_otp_codes (
organization_id = curr_org . organization_id ,
limit = limit ,
2025-11-07 09:38:52 -08:00
valid_lifespan_minutes = None ,
2025-10-29 20:49:25 -07:00
otp_type = otp_type ,
workflow_run_id = workflow_run_id ,
)
return codes
2025-05-18 12:43:22 -07:00
@legacy_base_router.post ( " /credentials " )
@legacy_base_router.post ( " /credentials/ " , include_in_schema = False )
@base_router.post (
2025-05-15 19:49:42 -07:00
" /credentials " ,
response_model = CredentialResponse ,
2025-05-18 12:43:22 -07:00
status_code = 201 ,
summary = " Create credential " ,
description = " Creates a new credential for the current organization " ,
2025-05-15 19:49:42 -07:00
tags = [ " Credentials " ] ,
openapi_extra = {
2025-05-18 12:43:22 -07:00
" x-fern-sdk-method-name " : " create_credential " ,
2025-05-18 13:45:46 -07:00
" x-fern-examples " : [
{
" code-samples " : [
2025-10-28 17:43:19 -06:00
{ " sdk " : " python " , " code " : CREATE_CREDENTIAL_CODE_SAMPLE_PYTHON } ,
{ " sdk " : " python " , " code " : CREATE_CREDENTIAL_CODE_SAMPLE_CREDIT_CARD_PYTHON } ,
{ " sdk " : " typescript " , " code " : CREATE_CREDENTIAL_CODE_SAMPLE_TS } ,
{ " sdk " : " typescript " , " code " : CREATE_CREDENTIAL_CODE_SAMPLE_CREDIT_CARD_TS } ,
2025-05-18 13:45:46 -07:00
]
}
] ,
2025-05-15 19:49:42 -07:00
} ,
)
2025-05-18 13:45:46 -07:00
@base_router.post (
" /credentials/ " ,
response_model = CredentialResponse ,
status_code = 201 ,
include_in_schema = False ,
)
2025-05-18 12:43:22 -07:00
async def create_credential (
2025-10-08 14:39:15 -06:00
background_tasks : BackgroundTasks ,
2025-05-18 12:43:22 -07:00
data : CreateCredentialRequest = Body (
2025-05-15 19:49:42 -07:00
. . . ,
2025-05-18 12:43:22 -07:00
description = " The credential data to create " ,
example = {
" name " : " My Credential " ,
" credential_type " : " PASSWORD " ,
" credential " : { " username " : " user@example.com " , " password " : " securepassword123 " , " totp " : " JBSWY3DPEHPK3PXP " } ,
} ,
openapi_extra = { " x-fern-sdk-parameter-name " : " data " } ,
2025-05-15 19:49:42 -07:00
) ,
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > CredentialResponse :
2025-11-04 10:29:51 -07:00
credential_service = await _get_credential_vault_service ( )
2025-10-10 10:10:18 -06:00
credential = await credential_service . create_credential ( organization_id = current_org . organization_id , data = data )
2025-05-15 19:49:42 -07:00
2025-10-10 10:10:18 -06:00
if credential . vault_type == CredentialVaultType . BITWARDEN :
# Early resyncing the Bitwarden vault
background_tasks . add_task ( fetch_credential_item_background , credential . item_id )
2025-10-08 14:39:15 -06:00
2025-05-18 12:43:22 -07:00
if data . credential_type == CredentialType . PASSWORD :
2025-05-15 19:49:42 -07:00
credential_response = PasswordCredentialResponse (
2025-05-18 12:43:22 -07:00
username = data . credential . username ,
2025-10-08 11:38:34 -07:00
totp_type = data . credential . totp_type if hasattr ( data . credential , " totp_type " ) else " none " ,
2025-05-15 19:49:42 -07:00
)
return CredentialResponse (
credential = credential_response ,
credential_id = credential . credential_id ,
2025-05-18 12:43:22 -07:00
credential_type = data . credential_type ,
name = data . name ,
2025-05-15 19:49:42 -07:00
)
2025-05-18 12:43:22 -07:00
elif data . credential_type == CredentialType . CREDIT_CARD :
2025-05-15 19:49:42 -07:00
credential_response = CreditCardCredentialResponse (
2025-05-18 12:43:22 -07:00
last_four = data . credential . card_number [ - 4 : ] ,
brand = data . credential . card_brand ,
2025-05-15 19:49:42 -07:00
)
return CredentialResponse (
credential = credential_response ,
credential_id = credential . credential_id ,
2025-05-18 12:43:22 -07:00
credential_type = data . credential_type ,
name = data . name ,
2025-05-15 19:49:42 -07:00
)
2025-10-09 11:39:01 -06:00
else :
raise HTTPException ( status_code = 400 , detail = f " Unsupported credential type: { data . credential_type } " )
2025-05-15 19:49:42 -07:00
@legacy_base_router.delete ( " /credentials/ {credential_id} " )
@legacy_base_router.delete ( " /credentials/ {credential_id} / " , include_in_schema = False )
@base_router.post (
" /credentials/ {credential_id} /delete " ,
status_code = 204 ,
summary = " Delete credential " ,
description = " Deletes a specific credential by its ID " ,
tags = [ " Credentials " ] ,
openapi_extra = {
" x-fern-sdk-method-name " : " delete_credential " ,
2025-10-28 17:43:19 -06:00
" x-fern-examples " : [
{
" code-samples " : [
{ " sdk " : " python " , " code " : DELETE_CREDENTIAL_CODE_SAMPLE_PYTHON } ,
{ " sdk " : " typescript " , " code " : DELETE_CREDENTIAL_CODE_SAMPLE_TS } ,
]
}
] ,
2025-05-15 19:49:42 -07:00
} ,
)
2025-05-18 13:45:46 -07:00
@base_router.post (
" /credentials/ {credential_id} /delete/ " ,
status_code = 204 ,
include_in_schema = False ,
)
2025-05-15 19:49:42 -07:00
async def delete_credential (
2025-10-14 20:25:22 -06:00
background_tasks : BackgroundTasks ,
2025-05-15 19:49:42 -07:00
credential_id : str = Path (
. . . ,
description = " The unique identifier of the credential to delete " ,
2025-06-13 22:58:55 -07:00
examples = [ " cred_1234567890 " ] ,
2025-05-15 19:49:42 -07:00
openapi_extra = { " x-fern-sdk-parameter-name " : " credential_id " } ,
) ,
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > None :
credential = await app . DATABASE . get_credential (
credential_id = credential_id , organization_id = current_org . organization_id
)
if not credential :
raise HTTPException ( status_code = 404 , detail = f " Credential not found, credential_id= { credential_id } " )
2025-10-10 10:10:18 -06:00
vault_type = credential . vault_type or CredentialVaultType . BITWARDEN
credential_service = app . CREDENTIAL_VAULT_SERVICES . get ( vault_type )
if not credential_service :
raise HTTPException ( status_code = 400 , detail = " Unsupported credential storage type " )
await credential_service . delete_credential ( credential )
2025-05-15 19:49:42 -07:00
2025-10-14 20:25:22 -06:00
# Schedule background cleanup if the service implements it
background_tasks . add_task ( credential_service . post_delete_credential_item , credential . item_id )
2025-05-15 19:49:42 -07:00
return None
2025-05-18 12:43:22 -07:00
@legacy_base_router.get ( " /credentials/ {credential_id} " )
@legacy_base_router.get ( " /credentials/ {credential_id} / " , include_in_schema = False )
@base_router.get (
" /credentials/ {credential_id} " ,
2025-05-15 19:49:42 -07:00
response_model = CredentialResponse ,
2025-05-18 12:43:22 -07:00
summary = " Get credential by ID " ,
description = " Retrieves a specific credential by its ID " ,
2025-05-15 19:49:42 -07:00
tags = [ " Credentials " ] ,
openapi_extra = {
2025-05-18 12:43:22 -07:00
" x-fern-sdk-method-name " : " get_credential " ,
2025-10-28 17:43:19 -06:00
" x-fern-examples " : [
{
" code-samples " : [
{ " sdk " : " python " , " code " : GET_CREDENTIAL_CODE_SAMPLE_PYTHON } ,
{ " sdk " : " typescript " , " code " : GET_CREDENTIAL_CODE_SAMPLE_TS } ,
]
}
] ,
2025-05-15 19:49:42 -07:00
} ,
)
2025-05-18 13:45:46 -07:00
@base_router.get (
" /credentials/ {credential_id} / " ,
response_model = CredentialResponse ,
include_in_schema = False ,
)
2025-05-18 12:43:22 -07:00
async def get_credential (
credential_id : str = Path (
2025-05-15 19:49:42 -07:00
. . . ,
2025-05-18 12:43:22 -07:00
description = " The unique identifier of the credential " ,
2025-06-13 22:58:55 -07:00
examples = [ " cred_1234567890 " ] ,
2025-05-18 12:43:22 -07:00
openapi_extra = { " x-fern-sdk-parameter-name " : " credential_id " } ,
2025-05-15 19:49:42 -07:00
) ,
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > CredentialResponse :
2025-11-04 10:29:51 -07:00
credential = await app . DATABASE . get_credential (
credential_id = credential_id , organization_id = current_org . organization_id
)
if not credential :
raise HTTPException ( status_code = 404 , detail = " Credential not found " )
2025-10-10 10:10:18 -06:00
2025-11-04 10:29:51 -07:00
return _convert_to_response ( credential )
2025-05-18 12:43:22 -07:00
@legacy_base_router.get ( " /credentials " )
@legacy_base_router.get ( " /credentials/ " , include_in_schema = False )
@base_router.get (
" /credentials " ,
response_model = list [ CredentialResponse ] ,
summary = " Get all credentials " ,
description = " Retrieves a paginated list of credentials for the current organization " ,
tags = [ " Credentials " ] ,
openapi_extra = {
" x-fern-sdk-method-name " : " get_credentials " ,
2025-10-28 17:43:19 -06:00
" x-fern-examples " : [
{
" code-samples " : [
{ " sdk " : " python " , " code " : GET_CREDENTIALS_CODE_SAMPLE_PYTHON } ,
{ " sdk " : " typescript " , " code " : GET_CREDENTIALS_CODE_SAMPLE_TS } ,
]
}
] ,
2025-05-18 12:43:22 -07:00
} ,
)
2025-05-18 13:45:46 -07:00
@base_router.get (
" /credentials/ " ,
response_model = list [ CredentialResponse ] ,
include_in_schema = False ,
)
2025-05-18 12:43:22 -07:00
async def get_credentials (
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
page : int = Query (
1 ,
ge = 1 ,
description = " Page number for pagination " ,
2025-06-13 22:58:55 -07:00
examples = [ 1 ] ,
2025-05-18 12:43:22 -07:00
openapi_extra = { " x-fern-sdk-parameter-name " : " page " } ,
) ,
page_size : int = Query (
10 ,
ge = 1 ,
description = " Number of items per page " ,
2025-06-13 22:58:55 -07:00
examples = [ 10 ] ,
2025-05-18 12:43:22 -07:00
openapi_extra = { " x-fern-sdk-parameter-name " : " page_size " } ,
) ,
) - > list [ CredentialResponse ] :
2025-11-04 10:29:51 -07:00
credentials = await app . DATABASE . get_credentials ( current_org . organization_id , page = page , page_size = page_size )
return [ _convert_to_response ( credential ) for credential in credentials ]
2025-08-05 07:34:26 -07:00
@base_router.get (
2025-08-05 23:33:08 +08:00
" /credentials/onepassword/get " ,
2025-08-05 07:34:26 -07:00
response_model = CreateOnePasswordTokenResponse ,
summary = " Get OnePassword service account token " ,
description = " Retrieves the current OnePassword service account token for the organization. " ,
2025-08-14 09:08:47 -07:00
include_in_schema = False ,
2025-08-05 07:34:26 -07:00
)
@base_router.get (
2025-08-05 23:33:08 +08:00
" /credentials/onepassword/get/ " ,
2025-08-05 07:34:26 -07:00
response_model = CreateOnePasswordTokenResponse ,
include_in_schema = False ,
)
async def get_onepassword_token (
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > CreateOnePasswordTokenResponse :
"""
Get the current OnePassword service account token for the organization .
"""
try :
auth_token = await app . DATABASE . get_valid_org_auth_token (
organization_id = current_org . organization_id ,
2025-09-26 16:35:47 -07:00
token_type = OrganizationAuthTokenType . onepassword_service_account . value ,
2025-08-05 07:34:26 -07:00
)
if not auth_token :
raise HTTPException (
status_code = 404 ,
detail = " No OnePassword service account token found for this organization " ,
)
return CreateOnePasswordTokenResponse ( token = auth_token )
except HTTPException :
raise
except Exception as e :
LOG . error (
" Failed to get OnePassword service account token " ,
organization_id = current_org . organization_id ,
error = str ( e ) ,
exc_info = True ,
)
raise HTTPException (
status_code = 500 ,
detail = f " Failed to get OnePassword service account token: { str ( e ) } " ,
)
@base_router.post (
2025-08-05 23:33:08 +08:00
" /credentials/onepassword/create " ,
2025-08-05 07:34:26 -07:00
response_model = CreateOnePasswordTokenResponse ,
summary = " Create or update OnePassword service account token " ,
description = " Creates or updates a OnePassword service account token for the current organization. Only one valid token is allowed per organization. " ,
2025-08-14 09:08:47 -07:00
include_in_schema = False ,
2025-08-05 07:34:26 -07:00
)
@base_router.post (
2025-08-05 23:33:08 +08:00
" /credentials/onepassword/create/ " ,
2025-08-05 07:34:26 -07:00
response_model = CreateOnePasswordTokenResponse ,
include_in_schema = False ,
)
async def update_onepassword_token (
2025-08-14 08:29:13 -07:00
data : CreateOnePasswordTokenRequest ,
2025-08-05 07:34:26 -07:00
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > CreateOnePasswordTokenResponse :
"""
Create or update a OnePassword service account token for the current organization .
This endpoint ensures only one valid OnePassword token exists per organization .
If a valid token already exists , it will be invalidated before creating the new one .
"""
try :
# Invalidate any existing valid OnePassword tokens for this organization
await app . DATABASE . invalidate_org_auth_tokens (
organization_id = current_org . organization_id ,
token_type = OrganizationAuthTokenType . onepassword_service_account ,
)
# Create the new token
auth_token = await app . DATABASE . create_org_auth_token (
organization_id = current_org . organization_id ,
token_type = OrganizationAuthTokenType . onepassword_service_account ,
token = data . token ,
)
LOG . info (
" Created or updated OnePassword service account token " ,
organization_id = current_org . organization_id ,
token_id = auth_token . id ,
)
return CreateOnePasswordTokenResponse ( token = auth_token )
except Exception as e :
LOG . error (
" Failed to create or update OnePassword service account token " ,
organization_id = current_org . organization_id ,
error = str ( e ) ,
exc_info = True ,
)
raise HTTPException (
status_code = 500 ,
detail = f " Failed to create or update OnePassword service account token: { str ( e ) } " ,
)
2025-09-23 10:16:48 -06:00
@base_router.get (
" /credentials/azure_credential/get " ,
response_model = AzureClientSecretCredentialResponse ,
summary = " Get Azure Client Secret Credential " ,
description = " Retrieves the current Azure Client Secret Credential for the organization. " ,
include_in_schema = False ,
)
@base_router.get (
" /credentials/azure_credential/get/ " ,
response_model = AzureClientSecretCredentialResponse ,
include_in_schema = False ,
)
async def get_azure_client_secret_credential (
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > AzureClientSecretCredentialResponse :
"""
Get the current Azure Client Secret Credential for the organization .
"""
try :
auth_token = await app . DATABASE . get_valid_org_auth_token (
organization_id = current_org . organization_id ,
2025-09-26 16:35:47 -07:00
token_type = OrganizationAuthTokenType . azure_client_secret_credential . value ,
2025-09-23 10:16:48 -06:00
)
if not auth_token :
raise HTTPException (
status_code = 404 ,
detail = " No Azure Client Secret Credential found for this organization " ,
)
return AzureClientSecretCredentialResponse ( token = auth_token )
except HTTPException :
raise
except Exception as e :
LOG . error (
" Failed to get Azure Client Secret Credential " ,
organization_id = current_org . organization_id ,
error = str ( e ) ,
exc_info = True ,
)
raise HTTPException (
status_code = 500 ,
detail = f " Failed to get Azure Client Secret Credential: { str ( e ) } " ,
)
@base_router.post (
" /credentials/azure_credential/create " ,
response_model = AzureClientSecretCredentialResponse ,
summary = " Create or update Azure Client Secret Credential " ,
description = " Creates or updates a Azure Client Secret Credential for the current organization. Only one valid record is allowed per organization. " ,
include_in_schema = False ,
)
@base_router.post (
" /credentials/azure_credential/create/ " ,
response_model = AzureClientSecretCredentialResponse ,
include_in_schema = False ,
)
async def update_azure_client_secret_credential (
request : CreateAzureClientSecretCredentialRequest ,
current_org : Organization = Depends ( org_auth_service . get_current_org ) ,
) - > AzureClientSecretCredentialResponse :
"""
Create or update an Azure Client Secret Credential for the current organization .
This endpoint ensures only one valid Azure Client Secret Credential exists per organization .
If a valid token already exists , it will be invalidated before creating the new one .
"""
try :
# Invalidate any existing valid Azure Client Secret Credential for this organization
await app . DATABASE . invalidate_org_auth_tokens (
organization_id = current_org . organization_id ,
token_type = OrganizationAuthTokenType . azure_client_secret_credential ,
)
# Create the new Azure token
auth_token = await app . DATABASE . create_org_auth_token (
organization_id = current_org . organization_id ,
token_type = OrganizationAuthTokenType . azure_client_secret_credential ,
token = request . credential ,
)
LOG . info (
" Created or updated Azure Client Secret Credential " ,
organization_id = current_org . organization_id ,
token_id = auth_token . id ,
)
return AzureClientSecretCredentialResponse ( token = auth_token )
except Exception as e :
LOG . error (
" Failed to create or update Azure Client Secret Credential " ,
organization_id = current_org . organization_id ,
error = str ( e ) ,
exc_info = True ,
)
raise HTTPException (
status_code = 500 ,
detail = f " Failed to create or update Azure Client Secret Credential: { str ( e ) } " ,
)
2025-10-10 10:10:18 -06:00
2025-11-04 10:29:51 -07:00
async def _get_credential_vault_service ( ) - > CredentialVaultService :
if settings . CREDENTIAL_VAULT_TYPE == CredentialVaultType . BITWARDEN :
2025-10-10 10:10:18 -06:00
return app . BITWARDEN_CREDENTIAL_VAULT_SERVICE
elif settings . CREDENTIAL_VAULT_TYPE == CredentialVaultType . AZURE_VAULT :
if not app . AZURE_CREDENTIAL_VAULT_SERVICE :
raise HTTPException ( status_code = 400 , detail = " Azure Vault credential is not supported " )
return app . AZURE_CREDENTIAL_VAULT_SERVICE
else :
raise HTTPException ( status_code = 400 , detail = " Credential storage not supported " )
2025-11-04 10:29:51 -07:00
def _convert_to_response ( credential : Credential ) - > CredentialResponse :
if credential . credential_type == CredentialType . PASSWORD :
credential_response = PasswordCredentialResponse (
username = credential . username or credential . credential_id ,
totp_type = credential . totp_type ,
)
return CredentialResponse (
credential = credential_response ,
credential_id = credential . credential_id ,
credential_type = credential . credential_type ,
name = credential . name ,
)
elif credential . credential_type == CredentialType . CREDIT_CARD :
credential_response = CreditCardCredentialResponse (
last_four = credential . card_last4 or " **** " ,
brand = credential . card_brand or " Card Brand " ,
)
return CredentialResponse (
credential = credential_response ,
credential_id = credential . credential_id ,
credential_type = credential . credential_type ,
name = credential . name ,
)
else :
raise HTTPException ( status_code = 400 , detail = " Credential type not supported " )