Skip to content

Commit ec94e86

Browse files
Add authentication module and user management
Implement user registration, login, and token management features. Introduce user model and schemas for data validation. Update requirements and configuration for JWT settings and dependencies.
1 parent 22d1a4b commit ec94e86

13 files changed

+623
-16
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ wallets.dat
5555

5656
# Coverage
5757
coverage.xml
58-
htmlcov/
58+
htmlcov/
59+
test.db

README.md

+28-7
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,15 @@ docker-compose run --rm ravenchain pytest --cov=ravenchain
128128

129129
## 🎯 Roadmap
130130

131-
### Phase 1: Core Infrastructure Enhancement (Current)
131+
### Phase 1: Core Infrastructure Enhancement
132132
- [x] Basic blockchain implementation
133133
- [x] Wallet management system
134134
- [x] Command-line interface
135135
- [x] Transaction handling
136136
- [x] Unit test coverage
137137
- [x] Documentation improvements
138138

139-
### Phase 2: Data Persistence & API (Next)
139+
### Phase 2: Data Persistence & API
140140
- [x] Implement PostgreSQL for blockchain storage
141141
- [x] Design and implement FastAPI REST API
142142
- [x] Block endpoints
@@ -145,9 +145,9 @@ docker-compose run --rm ravenchain pytest --cov=ravenchain
145145
- [x] Mining endpoints
146146
- [x] API documentation with Swagger/OpenAPI
147147
- [x] Request rate limiting
148-
- [ ] API authentication system
148+
- [x] API authentication system
149149

150-
### Phase 3: Networking & Distribution
150+
### Phase 3: Networking & Distribution (Current)
151151
- [ ] P2P network implementation
152152
- [ ] Node discovery protocol
153153
- [ ] Block synchronization
@@ -156,7 +156,7 @@ docker-compose run --rm ravenchain pytest --cov=ravenchain
156156
- [ ] Network state management
157157
- [ ] Peer management system
158158

159-
### Phase 4: Smart Contracts & Advanced Features
159+
### Phase 4: Smart Contracts & Advanced Features (Next)
160160
- [ ] Basic smart contract engine
161161
- [ ] Contract deployment system
162162
- [ ] Standard contract templates
@@ -185,20 +185,41 @@ docker-compose run --rm ravenchain pytest --cov=ravenchain
185185
pytest tests/
186186
```
187187

188-
## 📖 API Documentation (Planned)
188+
## 📖 API Documentation
189189

190190
The REST API will provide the following endpoints:
191191

192192
```
193+
# Authentication Endpoints
194+
POST /api/v1/auth/register # Register a new user
195+
POST /api/v1/auth/login # Login and get tokens
196+
POST /api/v1/auth/refresh # Refresh access token
197+
GET /api/v1/auth/me # Get current user info
198+
PUT /api/v1/auth/me # Update current user info
199+
200+
# Admin Endpoints
201+
GET /api/v1/admin/users # List all users (admin only)
202+
GET /api/v1/admin/users/{id} # Get user details (admin only)
203+
PUT /api/v1/admin/users/{id} # Update user (admin only)
204+
DELETE /api/v1/admin/users/{id} # Delete user (admin only)
205+
206+
# Block Endpoints
193207
GET /api/v1/blocks # List blocks
194208
GET /api/v1/blocks/{hash} # Get block details
209+
GET /api/v1/blocks/latest # Get latest block
210+
211+
# Transaction Endpoints
195212
GET /api/v1/transactions # List transactions
196213
POST /api/v1/transactions # Create transaction
214+
215+
# Wallet Endpoints
197216
GET /api/v1/wallets/{address} # Get wallet info
217+
218+
# Mining Endpoints
198219
POST /api/v1/mine # Mine new block
199220
```
200221

201-
Detailed API documentation will be available via Swagger UI at `/docs`.
222+
Detailed API documentation is available via Swagger UI at `/docs`.
202223

203224
## 🤝 Contributing
204225

api/auth/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

api/auth/schemas.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from pydantic import BaseModel, EmailStr, Field, ConfigDict
2+
from typing import Optional
3+
from datetime import datetime
4+
5+
6+
class UserBase(BaseModel):
7+
username: str
8+
email: EmailStr
9+
10+
11+
class UserCreate(UserBase):
12+
password: str = Field(..., min_length=8)
13+
wallet_address: Optional[str] = None
14+
15+
16+
class UserUpdate(BaseModel):
17+
email: Optional[EmailStr] = None
18+
password: Optional[str] = Field(None, min_length=8)
19+
is_active: Optional[bool] = None
20+
wallet_address: Optional[str] = None
21+
22+
23+
class UserDB(UserBase):
24+
id: int
25+
is_active: bool
26+
is_admin: bool
27+
created_at: datetime
28+
last_login: Optional[datetime] = None
29+
wallet_address: Optional[str] = None
30+
31+
model_config = ConfigDict(from_attributes=True)
32+
33+
34+
class UserResponse(UserBase):
35+
id: int
36+
is_active: bool
37+
is_admin: bool
38+
created_at: datetime
39+
last_login: Optional[datetime] = None
40+
wallet_address: Optional[str] = None
41+
42+
model_config = ConfigDict(from_attributes=True)
43+
44+
45+
class Token(BaseModel):
46+
access_token: str
47+
refresh_token: str
48+
token_type: str = "bearer"
49+
50+
51+
class TokenPayload(BaseModel):
52+
sub: str # Subject (username)
53+
exp: int # Expiration time
54+
55+
56+
class RefreshToken(BaseModel):
57+
refresh_token: str

api/auth/utils.py

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from datetime import datetime, timedelta, timezone
2+
from typing import Optional, Union, Dict, Any
3+
from jose import jwt, JWTError
4+
from passlib.context import CryptContext
5+
from fastapi import Depends, HTTPException, status
6+
from fastapi.security import OAuth2PasswordBearer
7+
from sqlalchemy.orm import Session
8+
from api.database.models import User
9+
from api.dependencies import SessionLocal
10+
from config.settings import settings
11+
12+
# Password context for hashing and verifying passwords
13+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
14+
15+
# OAuth2 scheme for token authentication
16+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_PREFIX}/auth/login")
17+
18+
# JWT configuration
19+
SECRET_KEY = settings.SECRET_KEY
20+
ALGORITHM = "HS256"
21+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
22+
REFRESH_TOKEN_EXPIRE_DAYS = 7
23+
24+
25+
def get_db():
26+
"""Get a database session"""
27+
db = SessionLocal()
28+
try:
29+
yield db
30+
finally:
31+
db.close()
32+
33+
34+
def verify_password(plain_password: str, hashed_password: str) -> bool:
35+
"""Verify a password against its hash"""
36+
return pwd_context.verify(plain_password, hashed_password)
37+
38+
39+
def get_password_hash(password: str) -> str:
40+
"""Hash a password"""
41+
return pwd_context.hash(password)
42+
43+
44+
def get_user(db: Session, username: str) -> Optional[User]:
45+
"""Get a user by username"""
46+
return db.query(User).filter(User.username == username).first()
47+
48+
49+
def authenticate_user(db: Session, username: str, password: str) -> Union[User, bool]:
50+
"""Authenticate a user by username and password"""
51+
user = get_user(db, username)
52+
if not user or not verify_password(password, user.hashed_password):
53+
return False
54+
return user
55+
56+
57+
def create_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
58+
"""Create a JWT token"""
59+
to_encode = data.copy()
60+
61+
if expires_delta:
62+
expire = datetime.now(timezone.utc) + expires_delta
63+
else:
64+
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
65+
66+
to_encode.update({"exp": expire})
67+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
68+
return encoded_jwt
69+
70+
71+
def create_access_token(data: Dict[str, Any]) -> str:
72+
"""Create an access token"""
73+
return create_token(
74+
data,
75+
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
76+
)
77+
78+
79+
def create_refresh_token(data: Dict[str, Any]) -> str:
80+
"""Create a refresh token"""
81+
return create_token(
82+
data,
83+
expires_delta=timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
84+
)
85+
86+
87+
async def get_current_user(
88+
token: str = Depends(oauth2_scheme),
89+
db: Session = Depends(get_db)
90+
) -> User:
91+
"""Get the current authenticated user from the token"""
92+
credentials_exception = HTTPException(
93+
status_code=status.HTTP_401_UNAUTHORIZED,
94+
detail="Could not validate credentials",
95+
headers={"WWW-Authenticate": "Bearer"},
96+
)
97+
98+
try:
99+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
100+
username: str = payload.get("sub")
101+
if username is None:
102+
raise credentials_exception
103+
except JWTError:
104+
raise credentials_exception
105+
106+
user = get_user(db, username=username)
107+
if user is None:
108+
raise credentials_exception
109+
110+
# Update last login time
111+
user.last_login = datetime.now(timezone.utc)
112+
db.commit()
113+
114+
return user
115+
116+
117+
async def get_current_active_user(
118+
current_user: User = Depends(get_current_user)
119+
) -> User:
120+
"""Get the current active user"""
121+
if not current_user.is_active:
122+
raise HTTPException(
123+
status_code=status.HTTP_403_FORBIDDEN,
124+
detail="Inactive user"
125+
)
126+
return current_user
127+
128+
129+
async def get_admin_user(
130+
current_user: User = Depends(get_current_active_user)
131+
) -> User:
132+
"""Check if the current user is an admin"""
133+
if not current_user.is_admin:
134+
raise HTTPException(
135+
status_code=status.HTTP_403_FORBIDDEN,
136+
detail="Admin privileges required"
137+
)
138+
return current_user

api/database/models.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from sqlalchemy import Column, Integer, String, Float, ForeignKey, DateTime, LargeBinary
2+
from sqlalchemy import Column, Integer, String, Float, ForeignKey, DateTime, LargeBinary, Boolean
33
from sqlalchemy.orm import declarative_base
44
from sqlalchemy import orm
55

@@ -26,3 +26,16 @@ class BlockDB(Base):
2626
nonce = Column(Integer)
2727
hash = Column(String)
2828
transactions = orm.relationship("TransactionDB", backref="block", lazy="joined")
29+
30+
31+
class User(Base):
32+
__tablename__ = "users"
33+
id = Column(Integer, primary_key=True, index=True)
34+
username = Column(String, unique=True, index=True)
35+
email = Column(String, unique=True, index=True)
36+
hashed_password = Column(String)
37+
is_active = Column(Boolean, default=True)
38+
is_admin = Column(Boolean, default=False)
39+
created_at = Column(DateTime, default=datetime.now)
40+
last_login = Column(DateTime, nullable=True)
41+
wallet_address = Column(String, nullable=True) # Link to blockchain wallet

api/main.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from contextlib import asynccontextmanager
2-
from fastapi import FastAPI, HTTPException, Request
2+
from fastapi import FastAPI, HTTPException, Request, Depends
33
from fastapi.middleware.cors import CORSMiddleware
44
from sqlalchemy import inspect, text
5-
from api.routes import block_routes, mining_routes, transaction_routes, wallet_routes
5+
from api.routes import block_routes, mining_routes, transaction_routes, wallet_routes, auth_routes
66
from api.database.models import Base
77
from api.dependencies import engine, logger, initialize_blockchain, limiter
88
from config.settings import settings
99
from slowapi.errors import RateLimitExceeded
1010
from slowapi.middleware import SlowAPIMiddleware
1111
from slowapi import _rate_limit_exceeded_handler
12+
from api.auth.utils import get_current_active_user
1213

1314

1415
@asynccontextmanager
@@ -63,9 +64,28 @@ async def health_check(request: Request):
6364
return {"status": "healthy"}
6465

6566

66-
app.include_router(block_routes.blockRouter, prefix=settings.API_PREFIX, tags=["blocks"])
67-
app.include_router(mining_routes.miningRouter, prefix=settings.API_PREFIX, tags=["mining"])
67+
app.include_router(auth_routes.authRouter, prefix=settings.API_PREFIX, tags=["auth"])
6868
app.include_router(
69-
transaction_routes.transactionRouter, prefix=settings.API_PREFIX, tags=["transactions"]
69+
block_routes.blockRouter,
70+
prefix=settings.API_PREFIX,
71+
tags=["blocks"],
72+
dependencies=[Depends(get_current_active_user)]
73+
)
74+
app.include_router(
75+
mining_routes.miningRouter,
76+
prefix=settings.API_PREFIX,
77+
tags=["mining"],
78+
dependencies=[Depends(get_current_active_user)]
79+
)
80+
app.include_router(
81+
transaction_routes.transactionRouter,
82+
prefix=settings.API_PREFIX,
83+
tags=["transactions"],
84+
dependencies=[Depends(get_current_active_user)]
85+
)
86+
app.include_router(
87+
wallet_routes.walletRouter,
88+
prefix=settings.API_PREFIX,
89+
tags=["wallets"],
90+
dependencies=[Depends(get_current_active_user)]
7091
)
71-
app.include_router(wallet_routes.walletRouter, prefix=settings.API_PREFIX, tags=["wallets"])

0 commit comments

Comments
 (0)