Skip to content

Commit 7a53301

Browse files
committed
Implement Firebase authentication and user validation in API endpoints
1 parent 1941535 commit 7a53301

4 files changed

Lines changed: 102 additions & 15 deletions

File tree

app/api/v1/auth.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import firebase_admin
2+
from firebase_admin import auth, credentials
3+
from fastapi import Depends, HTTPException, status
4+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
5+
import os
6+
7+
# Skema Bearer Token (Authorization: Bearer <token>)
8+
security = HTTPBearer()
9+
10+
# Variable global biar init cuma sekali
11+
firebase_app = None
12+
13+
def init_firebase():
14+
"""Inisialisasi Firebase Admin dengan Service Account"""
15+
global firebase_app
16+
17+
# Path file di dalam Docker (sesuai volume mapping kita kemarin)
18+
cred_path = "/code/app/serviceAccountKey.json"
19+
20+
if not os.path.exists(cred_path):
21+
print(f"⚠️ WARNING: File {cred_path} tidak ditemukan! Auth tidak akan jalan.")
22+
return
23+
24+
try:
25+
if not firebase_admin._apps:
26+
cred = credentials.Certificate(cred_path)
27+
firebase_app = firebase_admin.initialize_app(cred)
28+
print("🔥 Firebase Admin Initialized!")
29+
except Exception as e:
30+
print(f"❌ Error Init Firebase: {e}")
31+
32+
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
33+
"""
34+
Dependency untuk memvalidasi Token Firebase.
35+
Mengembalikan UID user jika valid.
36+
"""
37+
token = credentials.credentials
38+
try:
39+
# Minta Firebase verifikasi token ini valid atau palsu
40+
decoded_token = auth.verify_id_token(token)
41+
uid = decoded_token['uid']
42+
return uid
43+
except auth.ExpiredIdTokenError:
44+
raise HTTPException(
45+
status_code=status.HTTP_401_UNAUTHORIZED,
46+
detail="Token Expired. Silakan login ulang di HP.",
47+
)
48+
except Exception as e:
49+
raise HTTPException(
50+
status_code=status.HTTP_401_UNAUTHORIZED,
51+
detail="Token Tidak Valid / Palsu.",
52+
)

app/api/v1/devices.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
# --- IMPORT RATE LIMITER ---
77
from fastapi_limiter.depends import RateLimiter
88

9-
# --- IMPORT DATABASE ---
10-
from app.core.database import get_db
9+
# --- IMPORT AUTH (FIX PATH) ---
10+
from app.api.v1.auth import get_current_user # <-- Path harus 'app.auth'
11+
12+
# --- IMPORT DATABASE (FIX PATH) ---
13+
from app.core.database import get_db # <-- Path harus 'app.db.database'
1114
from app.models.device import Device
1215

13-
# --- PERBAIKAN VITAL DISINI ---
14-
# Kita import 'mqtt_client' karena itu nama variabel asli di file mqtt/client.py
16+
# --- IMPORT MQTT ---
1517
from app.mqtt.client import mqtt_client
1618

1719
router = APIRouter()
@@ -30,11 +32,9 @@ class AutoRegisterSchema(BaseModel):
3032
@router.post("/claim", dependencies=[Depends(RateLimiter(times=5, seconds=60))])
3133
def claim_device(
3234
claim_data: UserClaimSchema,
33-
db: Session = Depends(get_db)
35+
db: Session = Depends(get_db),
36+
user_uid: str = Depends(get_current_user) # <-- Wajib Login
3437
):
35-
# Hardcode user sementara
36-
user_uid = "TEST_USER_UID_001"
37-
3838
clean_id = claim_data.device_id.strip().upper()
3939
clean_pin = claim_data.pin_code.strip()
4040

@@ -43,6 +43,7 @@ def claim_device(
4343
if not device:
4444
raise HTTPException(status_code=404, detail=f"Alat {clean_id} tidak ditemukan.")
4545

46+
# Cek apakah sudah ada yang punya
4647
if device.owner_uid:
4748
if device.owner_uid == user_uid:
4849
return {"message": "Alat ini memang sudah punya kamu kok."}
@@ -51,6 +52,7 @@ def claim_device(
5152
if device.pin_code != clean_pin:
5253
raise HTTPException(status_code=400, detail="PIN Salah!")
5354

55+
# SAH: Pindahkan kepemilikan
5456
device.owner_uid = user_uid
5557
device.device_name = "Alat Baru Saya"
5658
db.commit()
@@ -63,7 +65,8 @@ def claim_device(
6365
def control_relay(
6466
device_id: str,
6567
state: str,
66-
db: Session = Depends(get_db)
68+
db: Session = Depends(get_db),
69+
user_uid: str = Depends(get_current_user) # <-- Wajib Login
6770
):
6871
clean_id = device_id.strip().upper()
6972

@@ -75,16 +78,20 @@ def control_relay(
7578
if not device:
7679
raise HTTPException(status_code=404, detail="Alat tidak ditemukan.")
7780

81+
# --- SECURITY CHECK (PENTING!) ---
82+
# Pastikan yang request adalah pemilik alat
83+
if device.owner_uid != user_uid:
84+
raise HTTPException(status_code=403, detail="Eits! Bukan alat kamu, jangan iseng ya!")
85+
7886
topic = f"alat/{clean_id}/command"
7987
payload = f'{{"relay": "{state}"}}'
8088

81-
# --- PAKAI mqtt_client YANG BENAR ---
8289
mqtt_client.publish(topic, payload)
8390

8491
return {"message": "Perintah dikirim", "topic": topic, "state": state}
8592

8693

87-
# --- 3. ENDPOINT AUTO REGISTER (ESP32) ---
94+
# --- 3. ENDPOINT AUTO REGISTER (ESP32 - Machine to Machine) ---
8895
@router.post("/auto-register", dependencies=[Depends(RateLimiter(times=5, seconds=60))])
8996
def auto_register_device(
9097
data: AutoRegisterSchema,

app/api/v1/logs.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,46 @@
55
# --- IMPORT RATE LIMITER ---
66
from fastapi_limiter.depends import RateLimiter
77

8-
from app.core.database import get_db
8+
# --- IMPORT AUTH (SATPAM) ---
9+
from app.api.v1.auth import get_current_user # <--- Tambah ini
10+
11+
# --- IMPORT DATABASE & MODEL ---
12+
from app.core.database import get_db # <--- Fix path ke app.db
913
from app.crud import log as crud_log
1014
from app.schemas.log import LogResponse
15+
from app.models.device import Device # <--- Import Device buat cek owner
1116

1217
router = APIRouter()
1318

14-
# --- TEMPELKAN DI SINI (dependencies) ---
1519
# times=5, seconds=10 artinya: Max 5 request dalam 10 detik.
1620
@router.get("/{device_id}", response_model=List[LogResponse], dependencies=[Depends(RateLimiter(times=5, seconds=10))])
1721
def read_device_logs(
1822
device_id: str,
1923
limit: int = 20,
20-
db: Session = Depends(get_db)
24+
db: Session = Depends(get_db),
25+
user_uid: str = Depends(get_current_user) # <--- Wajib Login
2126
):
22-
logs = crud_log.get_logs_by_device(db, device_id=device_id, limit=limit)
27+
# --- LOGIKA TAMBAHAN: CEK KEPEMILIKAN ---
28+
# Biar orang iseng gak bisa intip data kandang orang lain
29+
30+
# 1. Cari alatnya dulu di DB
31+
# Note: Kita gunakan strip().upper() biar konsisten sama ID penyimpanan
32+
clean_id = device_id.strip().upper()
33+
device = db.query(Device).filter(Device.device_id == clean_id).first()
34+
35+
# 2. Validasi
36+
if not device:
37+
# Kalau alat tidak ditemukan, return list kosong (atau 404)
38+
return []
39+
40+
# 3. Cek apakah User yang login == Pemilik Alat
41+
if device.owner_uid != user_uid:
42+
raise HTTPException(status_code=403, detail="Anda tidak memiliki akses ke alat ini.")
43+
44+
# 4. Kalau aman, baru ambil datanya
45+
logs = crud_log.get_logs_by_device(db, device_id=clean_id, limit=limit)
46+
2347
if not logs:
2448
return []
49+
2550
return logs

app/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from contextlib import asynccontextmanager
33
import redis.asyncio as redis
44
from fastapi_limiter import FastAPILimiter
5+
from app.api.v1.auth import init_firebase
56

67
# Import komponen buatan kita
78
from app.mqtt.client import start_mqtt
@@ -19,6 +20,8 @@ async def lifespan(app: FastAPI):
1920
# A. Nyalakan MQTT
2021
start_mqtt()
2122
print("✅ MQTT Listener Berjalan!")
23+
24+
init_firebase()
2225

2326
# B. Konek Redis & Init Rate Limiter
2427
try:

0 commit comments

Comments
 (0)