dbbfb747a4
- Replace InternalAuthService with TokenService using JWT tokens - Add support for token issuance, refresh, verification and revocation - Implement automatic signing key rotation with Redis storage - Add database migration checks for indexes and foreign key constraints - Update gRPC endpoints to use token-based authentication - Remove deprecated API key based authentication system - Add JSON Web Token support with HMAC-SHA256 signing - Implement refresh token handling with automatic rotation - Add token revocation by JTI and user ID - Update build configuration to include core proto files - Migrate database schema to handle token-based authentication - Add comprehensive token validation and verification logic
162 lines
5.0 KiB
Rust
162 lines
5.0 KiB
Rust
use tonic::{Request, Response, Status};
|
|
|
|
use crate::pb::core::token_service_server::TokenService as TokenServiceTrait;
|
|
use crate::pb::core::{
|
|
GetSigningKeysRequest, GetSigningKeysResponse, IssueTokenRequest, IssueTokenResponse,
|
|
RefreshTokenRequest, RefreshTokenResponse, RevokeTokenRequest, RevokeTokenResponse,
|
|
SigningKey, TokenClaims as PbTokenClaims, VerifyTokenRequest, VerifyTokenResponse,
|
|
revoke_token_request::Target,
|
|
};
|
|
use crate::service::internal_auth::TokenService;
|
|
|
|
pub struct TokenGrpcService {
|
|
service: TokenService,
|
|
}
|
|
|
|
impl TokenGrpcService {
|
|
pub fn new(service: TokenService) -> Self {
|
|
Self { service }
|
|
}
|
|
}
|
|
|
|
#[tonic::async_trait]
|
|
impl TokenServiceTrait for TokenGrpcService {
|
|
async fn issue_token(
|
|
&self,
|
|
request: Request<IssueTokenRequest>,
|
|
) -> Result<Response<IssueTokenResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
if req.user_id.is_empty() {
|
|
return Err(Status::invalid_argument("user_id is required"));
|
|
}
|
|
|
|
let ttl = if req.ttl_secs > 0 { req.ttl_secs } else { 3600 };
|
|
|
|
let tokens = self
|
|
.service
|
|
.issue_token(
|
|
&req.user_id,
|
|
ttl,
|
|
req.scopes,
|
|
req.extra,
|
|
)
|
|
.await
|
|
.map_err(|e| Status::internal(e.to_string()))?;
|
|
|
|
Ok(Response::new(IssueTokenResponse {
|
|
access_token: tokens.access_token,
|
|
refresh_token: tokens.refresh_token,
|
|
expires_at: tokens.expires_at,
|
|
key_id: tokens.key_id,
|
|
}))
|
|
}
|
|
|
|
async fn refresh_token(
|
|
&self,
|
|
request: Request<RefreshTokenRequest>,
|
|
) -> Result<Response<RefreshTokenResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
let tokens = self
|
|
.service
|
|
.refresh_token(&req.refresh_token, 3600)
|
|
.await
|
|
.map_err(|e| Status::unauthenticated(e.to_string()))?;
|
|
|
|
Ok(Response::new(RefreshTokenResponse {
|
|
access_token: tokens.access_token,
|
|
refresh_token: tokens.refresh_token,
|
|
expires_at: tokens.expires_at,
|
|
key_id: tokens.key_id,
|
|
}))
|
|
}
|
|
|
|
async fn revoke_token(
|
|
&self,
|
|
request: Request<RevokeTokenRequest>,
|
|
) -> Result<Response<RevokeTokenResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
match req.target {
|
|
Some(Target::Jti(jti)) => {
|
|
self.service
|
|
.revoke_by_jti(&jti, 86400)
|
|
.await
|
|
.map_err(|e| Status::internal(e.to_string()))?;
|
|
Ok(Response::new(RevokeTokenResponse { revoked_count: 1 }))
|
|
}
|
|
Some(Target::UserId(user_id)) => {
|
|
let count = self
|
|
.service
|
|
.revoke_user_tokens(&user_id)
|
|
.await
|
|
.map_err(|e| Status::internal(e.to_string()))?;
|
|
Ok(Response::new(RevokeTokenResponse {
|
|
revoked_count: count as i32,
|
|
}))
|
|
}
|
|
None => Err(Status::invalid_argument("target is required")),
|
|
}
|
|
}
|
|
|
|
async fn verify_token(
|
|
&self,
|
|
request: Request<VerifyTokenRequest>,
|
|
) -> Result<Response<VerifyTokenResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
match self
|
|
.service
|
|
.verify_token(&req.token)
|
|
.await
|
|
.map_err(|e| Status::internal(e.to_string()))?
|
|
{
|
|
Ok(claims) => Ok(Response::new(VerifyTokenResponse {
|
|
valid: true,
|
|
claims: Some(PbTokenClaims {
|
|
sub: claims.sub,
|
|
iss: claims.iss,
|
|
iat: claims.iat,
|
|
exp: claims.exp,
|
|
jti: claims.jti,
|
|
scope: claims.scope,
|
|
extra: claims.extra,
|
|
}),
|
|
reason: String::new(),
|
|
})),
|
|
Err(reason) => Ok(Response::new(VerifyTokenResponse {
|
|
valid: false,
|
|
claims: None,
|
|
reason,
|
|
})),
|
|
}
|
|
}
|
|
|
|
async fn get_signing_keys(
|
|
&self,
|
|
_request: Request<GetSigningKeysRequest>,
|
|
) -> Result<Response<GetSigningKeysResponse>, Status> {
|
|
let (keys, next_rotation_at) = self
|
|
.service
|
|
.get_signing_keys()
|
|
.await
|
|
.map_err(|e| Status::internal(e.to_string()))?;
|
|
|
|
Ok(Response::new(GetSigningKeysResponse {
|
|
keys: keys
|
|
.into_iter()
|
|
.map(|k| SigningKey {
|
|
kid: k.kid,
|
|
algorithm: k.algorithm,
|
|
key_material: k.key_material,
|
|
issued_at: k.issued_at,
|
|
expires_at: k.expires_at,
|
|
active: k.active,
|
|
})
|
|
.collect(),
|
|
next_rotation_at,
|
|
}))
|
|
}
|
|
}
|