Skip to content

Commit

Permalink
feat: adding in auth and server init
Browse files Browse the repository at this point in the history
  • Loading branch information
sminez committed Nov 26, 2024
1 parent ea60e48 commit d0f076e
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 110 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 2 additions & 8 deletions crates/ratings_new/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@ use serde::Deserialize;
/// Configuration for the general app center ratings backend service.
#[derive(Deserialize, Debug, Clone)]
pub struct Config {
/// Environment variables to use
pub env: String,
/// The host configuration
pub host: String,
/// The JWT secret value
pub jwt_secret: SecretString,
/// Log level to use
pub log_level: String,
/// The service name
pub name: String,
/// The port to run on
pub port: u16,
/// The URI of the postgres database
pub postgres_uri: String,
/// The JWT secret value
pub jwt_secret: SecretString,
/// The base URI for snapcraft.io
pub snapcraft_io_uri: String,
}
Expand Down
77 changes: 9 additions & 68 deletions crates/ratings_new/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
//! Application level context & state
use crate::config::Config;
use jsonwebtoken::{EncodingKey, Header};
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use crate::{
config::Config,
jwt::{Error, JwtEncoder},
};
use std::{collections::HashMap, sync::Arc};
use time::{Duration, OffsetDateTime};
use tokio::sync::{Mutex, Notify};
use tracing::error;

/// How many days until JWT info expires
static JWT_EXPIRY_DAYS: i64 = 1;

/// Errors that can happen while encoding and signing tokens with JWT.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("jwt: error decoding secret: {0}")]
DecodeSecretError(#[from] jsonwebtoken::errors::Error),

#[error(transparent)]
Envy(#[from] envy::Error),

#[error("jwt: an error occurred, but the reason was erased for security reasons")]
Erased,
}

pub struct Context {
pub config: Config,
Expand All @@ -33,55 +15,14 @@ pub struct Context {
}

impl Context {
pub fn new(config: &Config) -> Result<Self, Error> {
pub fn new(config: Config) -> Result<Self, Error> {
let jwt_encoder = JwtEncoder::from_secret(&config.jwt_secret)?;

Ok(Self {
config: Config::load()?,
jwt_encoder: JwtEncoder::from_secret(&config.jwt_secret)?,
config,
jwt_encoder,
http_client: reqwest::Client::new(),
category_updates: Default::default(),
})
}
}

/// Information representating a claim on a specific subject at a specific time
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
/// The subject
pub sub: String,
/// The expiration time
pub exp: usize,
}

impl Claims {
/// Creates a new claim with the current datetime for the subject given by `sub`.
pub fn new(sub: String) -> Self {
let exp = OffsetDateTime::now_utc() + Duration::days(JWT_EXPIRY_DAYS);
let exp = exp.unix_timestamp() as usize;

Self { sub, exp }
}
}

pub struct JwtEncoder {
encoding_key: EncodingKey,
}

impl JwtEncoder {
pub fn from_secret(secret: &SecretString) -> Result<JwtEncoder, Error> {
let encoding_key = EncodingKey::from_base64_secret(secret.expose_secret())?;

Ok(Self { encoding_key })
}

pub fn encode(&self, sub: String) -> Result<String, Error> {
let claims = Claims::new(sub);

match jsonwebtoken::encode(&Header::default(), &claims, &self.encoding_key) {
Ok(s) => Ok(s),
Err(e) => {
error!("unable to encode jwt: {e}");
Err(Error::Erased)
}
}
}
}
6 changes: 5 additions & 1 deletion crates/ratings_new/src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Config;
use sqlx::{postgres::PgPoolOptions, PgPool};
use sqlx::{postgres::PgPoolOptions, Connection, PgPool};
use thiserror::Error;
use tokio::sync::OnceCell;
use tracing::info;
Expand Down Expand Up @@ -75,6 +75,10 @@ pub async fn get_pool() -> Result<&'static PgPool> {
Ok(pool)
}

pub async fn check_db_conn() -> Result<()> {
conn!().ping().await.map_err(Into::into)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 3 additions & 2 deletions crates/ratings_new/src/db/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,10 @@ impl VoteSummary {
r"
WHERE votes.snap_id IN (
SELECT snap_categories.snap_id FROM snap_categories
WHERE snap_categories.category = $1)",
WHERE snap_categories.category = ",
)
.push_bind(category);
.push_bind(category)
.push(")");
}

builder.push(" GROUP BY votes.snap_id");
Expand Down
8 changes: 2 additions & 6 deletions crates/ratings_new/src/grpc/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ use tracing::error;
pub struct RatingService;

impl RatingService {
/// The paths which are accessible without authentication, if any
pub const PUBLIC_PATHS: [&'static str; 0] = [];

/// Converts this service into its corresponding server
pub fn to_server(self) -> AppServer<RatingService> {
AppServer::new(self)
pub fn new_server() -> AppServer<RatingService> {
AppServer::new(RatingService)
}
}

Expand Down
18 changes: 12 additions & 6 deletions crates/ratings_new/src/grpc/charts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ use tracing::error;
pub struct ChartService;

impl ChartService {
/// The paths which are accessible without authentication, if any
pub const PUBLIC_PATHS: [&'static str; 0] = [];

/// Converts this service into its corresponding server
pub fn to_server(self) -> ChartServer<ChartService> {
ChartServer::new(self)
pub fn new_server() -> ChartServer<ChartService> {
ChartServer::new(ChartService)
}
}

Expand Down Expand Up @@ -92,6 +88,16 @@ impl From<Rating> for PbRating {
}
}

impl From<PbRating> for Rating {
fn from(r: PbRating) -> Self {
Self {
snap_id: r.snap_id,
total_votes: r.total_votes,
ratings_band: RatingsBand::from_repr(r.ratings_band).unwrap(),
}
}
}

impl From<RatingsBand> for PbRatingsBand {
fn from(rb: RatingsBand) -> Self {
match rb {
Expand Down
30 changes: 25 additions & 5 deletions crates/ratings_new/src/grpc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
use crate::db;
use tonic::Status;
use crate::{db, jwt::JwtVerifier, middleware::AuthLayer, Context};
use std::net::SocketAddr;
use tonic::{transport::Server, Status};

pub mod app;
pub mod charts;
pub mod user;
mod app;
mod charts;
mod user;

use app::RatingService;
use charts::ChartService;
use user::UserService;

impl From<db::Error> for Status {
fn from(value: db::Error) -> Self {
Status::internal(value.to_string())
}
}

pub async fn run_server(ctx: Context) -> Result<(), Box<dyn std::error::Error>> {
let verifier = JwtVerifier::from_secret(&ctx.config.jwt_secret)?;
let addr: SocketAddr = ctx.config.socket().parse()?;

Server::builder()
.layer(AuthLayer::new(verifier))
.add_service(RatingService::new_server())
.add_service(ChartService::new_server())
.add_service(UserService::new_server(ctx))
.serve(addr)
.await?;

Ok(())
}
13 changes: 3 additions & 10 deletions crates/ratings_new/src/grpc/user.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
conn,
context::Claims,
db::{User, Vote},
jwt::Claims,
proto::user::{
user_server::{self, UserServer},
AuthenticateRequest, AuthenticateResponse, GetSnapVotesRequest, GetSnapVotesResponse,
Expand All @@ -25,15 +25,8 @@ pub struct UserService {
}

impl UserService {
/// The paths which are accessible without authentication, if any
pub const PUBLIC_PATHS: [&'static str; 2] = [
"ratings.features.user.User/Register",
"ratings.features.user.User/Authenticate",
];

/// Converts this service into its corresponding server
pub fn to_server(self) -> UserServer<UserService> {
UserServer::new(self)
pub fn new_server(ctx: Context) -> UserServer<UserService> {
UserServer::new(Self { ctx: Arc::new(ctx) })
}
}

Expand Down
Loading

0 comments on commit d0f076e

Please sign in to comment.