agentauthlayer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_auth/__init__.py +48 -0
- agent_auth/__main__.py +4 -0
- agent_auth/agents.py +40 -0
- agent_auth/audit.py +51 -0
- agent_auth/auth.py +36 -0
- agent_auth/cli.py +28 -0
- agent_auth/client.py +107 -0
- agent_auth/context.py +21 -0
- agent_auth/core.py +638 -0
- agent_auth/delegation.py +15 -0
- agent_auth/exceptions.py +72 -0
- agent_auth/models.py +72 -0
- agent_auth/policy.py +296 -0
- agent_auth/policy_service.py +176 -0
- agent_auth/principals.py +44 -0
- agent_auth/registry.py +90 -0
- agent_auth/session.py +135 -0
- agent_auth/storage.py +536 -0
- agent_auth/tokens.py +92 -0
- agent_auth/users.py +173 -0
- agentauthlayer-0.1.0.dist-info/METADATA +131 -0
- agentauthlayer-0.1.0.dist-info/RECORD +24 -0
- agentauthlayer-0.1.0.dist-info/WHEEL +5 -0
- agentauthlayer-0.1.0.dist-info/top_level.txt +1 -0
agent_auth/users.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import secrets
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from typing import Protocol, TypeVar
|
|
7
|
+
|
|
8
|
+
import bcrypt
|
|
9
|
+
|
|
10
|
+
from agent_auth.principals import user_scopes_for_role
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
UserT = TypeVar("UserT")
|
|
14
|
+
InviteT = TypeVar("InviteT")
|
|
15
|
+
|
|
16
|
+
DEFAULT_ADMIN_EMAIL = "admin@agentauth.dev"
|
|
17
|
+
LEGACY_ADMIN_EMAIL = "admin@agent-auth.local"
|
|
18
|
+
ADMIN_SCOPES = ["admin_agents", "admin_tokens", "read_audit", "admin_users"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UserStore(Protocol[UserT, InviteT]):
|
|
22
|
+
def get_admin_hash(self) -> str | None:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def set_admin_hash(self, password_hash: str) -> None:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def create_user(self, email: str, password_hash: str, role: str, invited_by: str | None = None) -> UserT:
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def get_user_by_email(self, email: str) -> UserT | None:
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def get_user_by_id(self, user_id: str) -> UserT | None:
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def list_users(self) -> list[UserT]:
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def update_password(self, user_id: str, password_hash: str) -> UserT | None:
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
def update_user(self, user_id: str, *, role: str | None = None, status: str | None = None) -> UserT | None:
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def create_invite(self, email: str, role: str, invited_by: str, invite_token: str, expires_at: datetime) -> InviteT:
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def get_invite_by_token(self, invite_token: str) -> InviteT | None:
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def list_invites(self) -> list[InviteT]:
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def mark_invite_accepted(self, invite_token: str) -> None:
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class EffectiveAccess:
|
|
61
|
+
user_id: str
|
|
62
|
+
email: str
|
|
63
|
+
role: str
|
|
64
|
+
scopes: list[str]
|
|
65
|
+
can_manage_users: bool
|
|
66
|
+
can_manage_agents: bool
|
|
67
|
+
can_manage_tokens: bool
|
|
68
|
+
can_read_audit: bool
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CoreUserService:
|
|
72
|
+
"""Core human-user identity, password, invite, and access helper logic."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, store: UserStore[UserT, InviteT]):
|
|
75
|
+
self.store = store
|
|
76
|
+
|
|
77
|
+
def create_admin(self, password: str) -> UserT:
|
|
78
|
+
password_hash = self.hash_password(password)
|
|
79
|
+
|
|
80
|
+
existing_default = self.store.get_user_by_email(DEFAULT_ADMIN_EMAIL)
|
|
81
|
+
if existing_default:
|
|
82
|
+
return self.store.update_password(existing_default.user_id, password_hash) or existing_default
|
|
83
|
+
|
|
84
|
+
existing_legacy = self.store.get_user_by_email(LEGACY_ADMIN_EMAIL)
|
|
85
|
+
if existing_legacy:
|
|
86
|
+
return self.store.update_password(existing_legacy.user_id, password_hash) or existing_legacy
|
|
87
|
+
|
|
88
|
+
return self.store.create_user(DEFAULT_ADMIN_EMAIL, password_hash, "admin", invited_by="bootstrap")
|
|
89
|
+
|
|
90
|
+
def authenticate(self, email: str, password: str) -> UserT | None:
|
|
91
|
+
user = self.store.get_user_by_email(email)
|
|
92
|
+
if not user and email == DEFAULT_ADMIN_EMAIL:
|
|
93
|
+
user = self.store.get_user_by_email(LEGACY_ADMIN_EMAIL)
|
|
94
|
+
elif not user and email == LEGACY_ADMIN_EMAIL:
|
|
95
|
+
user = self.store.get_user_by_email(DEFAULT_ADMIN_EMAIL)
|
|
96
|
+
if not user or getattr(user, "status", None) != "active":
|
|
97
|
+
return None
|
|
98
|
+
if not bcrypt.checkpw(password.encode(), getattr(user, "password_hash").encode()):
|
|
99
|
+
return None
|
|
100
|
+
return user
|
|
101
|
+
|
|
102
|
+
def invite_user(self, email: str, role: str, invited_by: str, ttl_hours: int = 24) -> InviteT:
|
|
103
|
+
token = secrets.token_urlsafe(32)
|
|
104
|
+
expires_at = datetime.now(timezone.utc) + timedelta(hours=ttl_hours)
|
|
105
|
+
return self.store.create_invite(email, role, invited_by, token, expires_at)
|
|
106
|
+
|
|
107
|
+
def get_invite_status(self, invite_token: str) -> InviteT | None:
|
|
108
|
+
return self.store.get_invite_by_token(invite_token)
|
|
109
|
+
|
|
110
|
+
def claim_invite(self, invite_token: str, password: str) -> UserT | None:
|
|
111
|
+
invite = self.store.get_invite_by_token(invite_token)
|
|
112
|
+
if not invite or getattr(invite, "accepted"):
|
|
113
|
+
return None
|
|
114
|
+
expires_at = getattr(invite, "expires_at")
|
|
115
|
+
if expires_at.tzinfo is None:
|
|
116
|
+
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
|
117
|
+
if expires_at <= datetime.now(timezone.utc):
|
|
118
|
+
return None
|
|
119
|
+
if self.store.get_user_by_email(getattr(invite, "email")):
|
|
120
|
+
return None
|
|
121
|
+
password_hash = self.hash_password(password)
|
|
122
|
+
user = self.store.create_user(getattr(invite, "email"), password_hash, getattr(invite, "role"), invited_by=getattr(invite, "invited_by"))
|
|
123
|
+
self.store.mark_invite_accepted(invite_token)
|
|
124
|
+
return user
|
|
125
|
+
|
|
126
|
+
def list_users(self) -> list[UserT]:
|
|
127
|
+
return self.store.list_users()
|
|
128
|
+
|
|
129
|
+
def list_invites(self) -> list[InviteT]:
|
|
130
|
+
return self.store.list_invites()
|
|
131
|
+
|
|
132
|
+
def get_user_by_id(self, user_id: str) -> UserT | None:
|
|
133
|
+
return self.store.get_user_by_id(user_id)
|
|
134
|
+
|
|
135
|
+
def get_user_by_email(self, email: str) -> UserT | None:
|
|
136
|
+
return self.store.get_user_by_email(email)
|
|
137
|
+
|
|
138
|
+
def get_current_user(self, principal_id: str | None, email: str | None) -> UserT | None:
|
|
139
|
+
if principal_id:
|
|
140
|
+
user = self.store.get_user_by_id(principal_id)
|
|
141
|
+
if user:
|
|
142
|
+
return user
|
|
143
|
+
normalized_email = email.lower() if email else None
|
|
144
|
+
if normalized_email:
|
|
145
|
+
user = self.store.get_user_by_email(normalized_email)
|
|
146
|
+
if user:
|
|
147
|
+
return user
|
|
148
|
+
if normalized_email in {DEFAULT_ADMIN_EMAIL, LEGACY_ADMIN_EMAIL}:
|
|
149
|
+
return self.store.get_user_by_email(DEFAULT_ADMIN_EMAIL) or self.store.get_user_by_email(LEGACY_ADMIN_EMAIL)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def effective_access(self, user: UserT) -> EffectiveAccess:
|
|
153
|
+
scopes = user_scopes_for_role(getattr(user, "role"))
|
|
154
|
+
return EffectiveAccess(
|
|
155
|
+
user_id=getattr(user, "user_id"),
|
|
156
|
+
email=getattr(user, "email"),
|
|
157
|
+
role=getattr(user, "role"),
|
|
158
|
+
scopes=scopes,
|
|
159
|
+
can_manage_users="admin_users" in scopes,
|
|
160
|
+
can_manage_agents="admin_agents" in scopes,
|
|
161
|
+
can_manage_tokens="admin_tokens" in scopes,
|
|
162
|
+
can_read_audit="read_audit" in scopes,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def update_user(self, user_id: str, *, role: str | None = None, status: str | None = None) -> UserT | None:
|
|
166
|
+
return self.store.update_user(user_id, role=role, status=status)
|
|
167
|
+
|
|
168
|
+
def change_password(self, user_id: str, password: str) -> UserT | None:
|
|
169
|
+
return self.store.update_password(user_id, self.hash_password(password))
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def hash_password(password: str) -> str:
|
|
173
|
+
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentauthlayer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library-first authentication and authorization SDK for AI agents
|
|
5
|
+
Author: Vaibhav Ahluwalia
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/VaibhavAhluwalia/Agent-Auth-
|
|
8
|
+
Project-URL: Repository, https://github.com/VaibhavAhluwalia/Agent-Auth-
|
|
9
|
+
Keywords: agents,auth,authorization,iam,security,sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: requests>=2.31.0
|
|
20
|
+
Requires-Dist: python-jose>=3.3.0
|
|
21
|
+
|
|
22
|
+
# Agent Auth SDK
|
|
23
|
+
|
|
24
|
+
A library-first authentication and authorization SDK for AI agents.
|
|
25
|
+
|
|
26
|
+
## What this package is
|
|
27
|
+
|
|
28
|
+
`agent-auth-sdk` is the reusable Python SDK layer of the Agent Auth platform. It is designed for developers who want to:
|
|
29
|
+
- register agents
|
|
30
|
+
- issue and verify access with the Agent Auth control plane
|
|
31
|
+
- sync tool definitions
|
|
32
|
+
- evaluate permissions
|
|
33
|
+
- embed policy enforcement into agent runtimes
|
|
34
|
+
|
|
35
|
+
This package is the **developer-facing library**, not the full FastAPI server or React admin UI.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install agent-auth-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For local development:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from agent_auth import AuthAPIClient
|
|
53
|
+
|
|
54
|
+
client = AuthAPIClient(
|
|
55
|
+
base_url="http://127.0.0.1:8002",
|
|
56
|
+
token="YOUR_ADMIN_OR_SERVICE_TOKEN",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
agent = client.create_agent(
|
|
60
|
+
agent_id="research-bot-01",
|
|
61
|
+
name="Research Bot 01",
|
|
62
|
+
owner="vaibhav@company.com",
|
|
63
|
+
role="research_agent",
|
|
64
|
+
scopes=[],
|
|
65
|
+
project_id="ai-platform",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
print(agent)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Environment variables
|
|
72
|
+
|
|
73
|
+
If you prefer, the SDK reads these automatically:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export AGENT_AUTH_URL=http://127.0.0.1:8002
|
|
77
|
+
export AGENT_AUTH_TOKEN=YOUR_ADMIN_OR_SERVICE_TOKEN
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from agent_auth import AuthAPIClient
|
|
84
|
+
|
|
85
|
+
client = AuthAPIClient()
|
|
86
|
+
print(client.health())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Common capabilities
|
|
90
|
+
|
|
91
|
+
### Sync tools from code
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
client.sync_tools([
|
|
95
|
+
{"action": "tool.search_web", "description": "Search the web"},
|
|
96
|
+
{"action": "docs.read", "description": "Read protected docs"},
|
|
97
|
+
])
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Evaluate access
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
decision = client.evaluate(
|
|
104
|
+
principal_id="research-bot-01",
|
|
105
|
+
action="tool.search_web",
|
|
106
|
+
resource="web/*",
|
|
107
|
+
role="research_agent",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
print(decision)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Package scope
|
|
114
|
+
|
|
115
|
+
This package currently exposes the reusable SDK and policy primitives under `agent_auth/`.
|
|
116
|
+
The full control plane server, admin UI, and demos remain in the same repository but are not part of the SDK package build.
|
|
117
|
+
|
|
118
|
+
## Dev-ready validation checklist
|
|
119
|
+
|
|
120
|
+
Before publishing, verify:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e .
|
|
124
|
+
python -c "import agent_auth; print(agent_auth.__all__)"
|
|
125
|
+
python -m build
|
|
126
|
+
pip install dist/*.whl
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Repository
|
|
130
|
+
|
|
131
|
+
- Source: <https://github.com/VaibhavAhluwalia/Agent-Auth->
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
agent_auth/__init__.py,sha256=c1P2iMJyKefBl6iKUsdEyWtt6EmtRlNVtOzUnoXZrqI,1454
|
|
2
|
+
agent_auth/__main__.py,sha256=7Hk2nPvyEFjvnclLhcq8Zw3pkyiZvhgC196qaT_ISVM,71
|
|
3
|
+
agent_auth/agents.py,sha256=uk9Ai6qIvYxmvKB9VCmtAlM3HqVAgyYncmr4h_pD61k,1075
|
|
4
|
+
agent_auth/audit.py,sha256=Be_opy-omGYMbUfi_trMkTAZpenxLK18vTMoQixSJyc,1399
|
|
5
|
+
agent_auth/auth.py,sha256=GwflUUnmyk8faO3TylvCVLx0NdAf5RBof9v0JPuibSI,1191
|
|
6
|
+
agent_auth/cli.py,sha256=iQeYtPG-CNFLEXMS8GiqAIpKHsJJCB7qSc-P6EH4VMI,1082
|
|
7
|
+
agent_auth/client.py,sha256=IlFE-vZ7bP4_LNdtg8mAksDkQlqwBqVGgmehUSCwaPs,3979
|
|
8
|
+
agent_auth/context.py,sha256=yxfBw9OazgywpCN7OUO3RKfJPF1-HR3YGL62jy-xWsI,455
|
|
9
|
+
agent_auth/core.py,sha256=0A2-gSDDZ7djPyKoCjcukl9j3-StHqDNo0DHta0S5Iw,22897
|
|
10
|
+
agent_auth/delegation.py,sha256=Q7r3bDQs78cpxtTob0E5_9cXnZMAi7sSl1yoNSE1UyE,375
|
|
11
|
+
agent_auth/exceptions.py,sha256=VDyR-UD1OZTZvWcwJEaRyC738LrRu191q3q7tTfJ5tw,1465
|
|
12
|
+
agent_auth/models.py,sha256=CrbxHDrPE-sNWKA5ZEAs7BCRQoPF5beod_ZDqav3cvU,1586
|
|
13
|
+
agent_auth/policy.py,sha256=OA9uOp84uvoKlljOCXrEFPhO_HRCYzvNNH3IC_7W3RQ,10370
|
|
14
|
+
agent_auth/policy_service.py,sha256=WZVuFBuaB947bEq5pj5FPym0ozMtFFDLWcGkQRN2OoA,6585
|
|
15
|
+
agent_auth/principals.py,sha256=aSNnm3M0TqJzEjKhZn9Mz7ToYVaMYJxRuI67dApxnY4,1110
|
|
16
|
+
agent_auth/registry.py,sha256=hmX91rKwZZdGhB_inr45sWJBSalBdu9ND9RgzT23FeU,2652
|
|
17
|
+
agent_auth/session.py,sha256=4WrGnfVn9tFz_tqDbMt5LgqLKd_JxBKguQOzRP9LVqo,4804
|
|
18
|
+
agent_auth/storage.py,sha256=96pOXbjTcRI6Nqkh1BNj7GryEhq-Xqgi9iU7R9Ul9gc,18400
|
|
19
|
+
agent_auth/tokens.py,sha256=J5_a0hbbNlk9HK2BsqAN0Uoc7845owjXFBbvqqpd9go,2435
|
|
20
|
+
agent_auth/users.py,sha256=HKgk_pzo8qESKlzCJdSQbWAPqoFo7UxwwV-nCfGN07U,6511
|
|
21
|
+
agentauthlayer-0.1.0.dist-info/METADATA,sha256=mnRnUbxDc5abi7yf2Y7Nxlci-wZGlM4l7LtLa8nBSGE,3055
|
|
22
|
+
agentauthlayer-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
23
|
+
agentauthlayer-0.1.0.dist-info/top_level.txt,sha256=ySYpKFR5CtHd9OgGNxTuV4-p8gpkSVJtzFCG0pzhGXw,11
|
|
24
|
+
agentauthlayer-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_auth
|