Skip to content

Commit e3b609e

Browse files
authored
Add support for service accounts (#112)
1 parent 187685e commit e3b609e

File tree

4 files changed

+52
-3
lines changed

4 files changed

+52
-3
lines changed

centml/sdk/api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ def get_deployment_usage(
134134
step=step,
135135
).values
136136

137+
def initialize_user(self):
138+
return self._api.setup_stripe_customer_payments_setup_post()
139+
137140

138141
@contextmanager
139142
def get_centml_client():

centml/sdk/auth.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,57 @@ def load_centml_cred():
5858

5959

6060
def get_centml_token():
61+
# Always use fresh client credentials if available
62+
if settings.CENTML_SERVICE_ACCOUNT_ID and settings.CENTML_SERVICE_ACCOUNT_SECRET:
63+
access_token = authenticate_with_client_credentials()
64+
if access_token is not None:
65+
return access_token
66+
else:
67+
sys.exit(
68+
"Could not authenticate with client credentials. Please check your service account configuration..."
69+
)
70+
71+
# Fall back to stored credentials for interactive flows
6172
cred = load_centml_cred()
6273
if not cred:
6374
sys.exit("CentML credentials not found. Please login...")
6475
exp_time = int(jwt.decode(cred["access_token"], options={"verify_signature": False})["exp"])
6576

6677
if time.time() >= exp_time - 100:
67-
cred = refresh_centml_token(cred["refresh_token"])
68-
if cred is None:
78+
# Check if we have a refresh token (interactive flow)
79+
refresh_token = cred.get("refresh_token")
80+
if refresh_token is not None:
81+
# Use refresh token for interactive authentication
82+
cred = refresh_centml_token(cred["refresh_token"])
83+
if cred is None:
84+
sys.exit("Could not refresh credentials. Please login and try again...")
85+
else:
6986
sys.exit("Could not refresh credentials. Please login and try again...")
7087

7188
return cred["access_token"]
7289

7390

91+
def authenticate_with_client_credentials():
92+
"""
93+
Authenticate using client credentials flow for service-to-service authentication.
94+
Returns access token if successful, None otherwise.
95+
"""
96+
if not settings.CENTML_SERVICE_ACCOUNT_ID or not settings.CENTML_SERVICE_ACCOUNT_SECRET:
97+
return None
98+
99+
params = {
100+
'grant_type': 'client_credentials',
101+
'client_id': settings.CENTML_SERVICE_ACCOUNT_ID,
102+
'client_secret': settings.CENTML_SERVICE_ACCOUNT_SECRET,
103+
'scope': 'openid profile email',
104+
}
105+
response = requests.post(settings.CENTML_SERVICE_ACCOUNT_TOKEN_URL, data=params, timeout=10)
106+
response.raise_for_status()
107+
response_data = response.json()
108+
access_token = response_data.get('access_token')
109+
return access_token
110+
111+
74112
def remove_centml_cred():
75113
if os.path.exists(settings.CENTML_CRED_FILE_PATH):
76114
os.remove(settings.CENTML_CRED_FILE_PATH)

centml/sdk/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from typing import Optional
23
from pathlib import Path
34
from pydantic_settings import BaseSettings, SettingsConfigDict
45

@@ -16,5 +17,12 @@ class Config(BaseSettings):
1617

1718
CENTML_WORKOS_CLIENT_ID: str = os.getenv("CENTML_WORKOS_CLIENT_ID", default="client_01JP5TWW2997MF8AYQXHJEGYR0")
1819

20+
# Long-term credentials - can be set via environment variables
21+
CENTML_SERVICE_ACCOUNT_SECRET: Optional[str] = os.getenv("CENTML_SERVICE_ACCOUNT_SECRET", default=None)
22+
CENTML_SERVICE_ACCOUNT_ID: Optional[str] = os.getenv("CENTML_SERVICE_ACCOUNT_ID", default=None)
23+
CENTML_SERVICE_ACCOUNT_TOKEN_URL: str = os.getenv(
24+
"CENTML_SERVICE_ACCOUNT_TOKEN_URL", default="https://signin.centml.com/oauth2/token"
25+
)
26+
1927

2028
settings = Config()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
setup(
1313
name='centml',
14-
version='0.4.4',
14+
version='0.4.5',
1515
packages=find_packages(),
1616
python_requires=">=3.10",
1717
long_description=open('README.md').read(),

0 commit comments

Comments
 (0)