diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c38ee778..d4ac9ac1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ The following command inserts a user into the `auth` state with admin privileges ```bash # the --key needs to be 16 alphanumeric characters -docker compose --file docker-compose.rendered.yml --project-name shuttle-dev exec auth /usr/local/bin/service --state=/var/lib/shuttle-auth init --name admin --key dh9z58jttoes3qvt +docker compose --file docker-compose.rendered.yml --project-name shuttle-dev exec auth /usr/local/bin/service --state=/var/lib/shuttle-auth init-admin --name admin --key dh9z58jttoes3qvt ``` Login to Shuttle service in a new terminal window from the root of the Shuttle directory: @@ -109,6 +109,14 @@ Login to Shuttle service in a new terminal window from the root of the Shuttle d cargo run --bin cargo-shuttle -- login --api-key "dh9z58jttoes3qvt" ``` +Finally, before gateway will be able to work with some projects, we need to create a user for it. +The following command inserts a gateway user into the `auth` state with deployer privileges: + +```bash +# the --key needs to be 16 alphanumeric characters +docker compose --file docker-compose.rendered.yml --project-name shuttle-dev exec auth /usr/local/bin/service --state=/var/lib/shuttle-auth init-deployer --name gateway --key gateway4deployes +``` + The [shuttle examples](https://github.com/shuttle-hq/shuttle-examples) are linked to the main repo as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules), to initialize it run the following commands: ```bash @@ -167,14 +175,14 @@ docker compose -f docker-compose.rendered.yml up provisioner This starts the provisioner and the auth service, while preventing `gateway` from starting up. Next up we need to insert an admin user into the `auth` state using the ID of the `auth` -container and the auth CLI `init` command: +container and the auth CLI `init-admin` command: ```bash AUTH_CONTAINER_ID=$(docker ps -qf "name=auth") docker exec $AUTH_CONTAINER_ID ./usr/local/bin/service \ --state=/var/lib/shuttle-auth \ - init --name admin --key dh9z58jttoes3qvt + init-admin --name admin --key dh9z58jttoes3qvt ``` > Note: if you have done this already for this container you will get a "UNIQUE constraint failed" diff --git a/auth/src/args.rs b/auth/src/args.rs index ec1db63e3..53d2da9bd 100644 --- a/auth/src/args.rs +++ b/auth/src/args.rs @@ -15,7 +15,8 @@ pub struct Args { #[derive(Subcommand, Debug)] pub enum Commands { Start(StartArgs), - Init(InitArgs), + InitAdmin(InitArgs), + InitDeployer(InitArgs), } #[derive(clap::Args, Debug, Clone)] diff --git a/auth/src/lib.rs b/auth/src/lib.rs index 2e3aa333f..d3f7695fa 100644 --- a/auth/src/lib.rs +++ b/auth/src/lib.rs @@ -16,9 +16,10 @@ use sqlx::{ }; use tracing::info; -use crate::{api::serve, user::AccountTier}; +use crate::api::serve; pub use api::ApiBuilder; pub use args::{Args, Commands, InitArgs}; +pub use user::AccountTier; pub const COOKIE_EXPIRATION: Duration = Duration::from_secs(60 * 60 * 24); // One day @@ -37,7 +38,7 @@ pub async fn start(pool: SqlitePool, args: StartArgs) -> io::Result<()> { Ok(()) } -pub async fn init(pool: SqlitePool, args: InitArgs) -> io::Result<()> { +pub async fn init(pool: SqlitePool, args: InitArgs, tier: AccountTier) -> io::Result<()> { let key = match args.key { Some(ref key) => ApiKey::parse(key).unwrap(), None => ApiKey::generate(), @@ -46,14 +47,15 @@ pub async fn init(pool: SqlitePool, args: InitArgs) -> io::Result<()> { query("INSERT INTO users (account_name, key, account_tier) VALUES (?1, ?2, ?3)") .bind(&args.name) .bind(&key) - .bind(AccountTier::Admin) + .bind(tier) .execute(&pool) .await .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; println!( - "`{}` created as super user with key: {}", + "`{}` created as {} with key: {}", args.name, + tier, key.as_ref() ); Ok(()) diff --git a/auth/src/main.rs b/auth/src/main.rs index 4a9136563..613dd3a0a 100644 --- a/auth/src/main.rs +++ b/auth/src/main.rs @@ -5,7 +5,7 @@ use shuttle_common::backends::tracing::setup_tracing; use sqlx::migrate::Migrator; use tracing::{info, trace}; -use shuttle_auth::{init, sqlite_init, start, Args, Commands}; +use shuttle_auth::{init, sqlite_init, start, AccountTier, Args, Commands}; pub static MIGRATIONS: Migrator = sqlx::migrate!("./migrations"); @@ -32,6 +32,7 @@ async fn main() -> io::Result<()> { match args.command { Commands::Start(args) => start(pool, args).await, - Commands::Init(args) => init(pool, args).await, + Commands::InitAdmin(args) => init(pool, args, AccountTier::Admin).await, + Commands::InitDeployer(args) => init(pool, args, AccountTier::Deployer).await, } } diff --git a/auth/src/user.rs b/auth/src/user.rs index d4d89f6a7..78e8dec9a 100644 --- a/auth/src/user.rs +++ b/auth/src/user.rs @@ -191,6 +191,7 @@ pub enum AccountTier { Pro, Team, Admin, + Deployer, } impl From for Vec { @@ -201,6 +202,12 @@ impl From for Vec { builder = builder.with_admin() } + if tier == AccountTier::Deployer { + builder = builder.with_deploy_rights(); + } else { + builder = builder.with_basic(); + } + builder.build() } } @@ -262,3 +269,114 @@ where } } } + +#[cfg(test)] +mod tests { + mod convert_tiers { + use shuttle_common::claims::Scope; + + use crate::user::AccountTier; + + #[test] + fn basic() { + let scopes: Vec = AccountTier::Basic.into(); + + assert_eq!( + scopes, + vec![ + Scope::Deployment, + Scope::DeploymentPush, + Scope::Logs, + Scope::Service, + Scope::ServiceCreate, + Scope::Project, + Scope::ProjectCreate, + Scope::Resources, + Scope::ResourcesWrite, + Scope::Secret, + Scope::SecretWrite, + ] + ); + } + + #[test] + fn pro() { + let scopes: Vec = AccountTier::Pro.into(); + + assert_eq!( + scopes, + vec![ + Scope::Deployment, + Scope::DeploymentPush, + Scope::Logs, + Scope::Service, + Scope::ServiceCreate, + Scope::Project, + Scope::ProjectCreate, + Scope::Resources, + Scope::ResourcesWrite, + Scope::Secret, + Scope::SecretWrite, + ] + ); + } + + #[test] + fn team() { + let scopes: Vec = AccountTier::Team.into(); + + assert_eq!( + scopes, + vec![ + Scope::Deployment, + Scope::DeploymentPush, + Scope::Logs, + Scope::Service, + Scope::ServiceCreate, + Scope::Project, + Scope::ProjectCreate, + Scope::Resources, + Scope::ResourcesWrite, + Scope::Secret, + Scope::SecretWrite, + ] + ); + } + + #[test] + fn admin() { + let scopes: Vec = AccountTier::Admin.into(); + + assert_eq!( + scopes, + vec![ + Scope::User, + Scope::UserCreate, + Scope::AcmeCreate, + Scope::CustomDomainCreate, + Scope::CustomDomainCertificateRenew, + Scope::GatewayCertificateRenew, + Scope::Admin, + Scope::Deployment, + Scope::DeploymentPush, + Scope::Logs, + Scope::Service, + Scope::ServiceCreate, + Scope::Project, + Scope::ProjectCreate, + Scope::Resources, + Scope::ResourcesWrite, + Scope::Secret, + Scope::SecretWrite, + ] + ); + } + + #[test] + fn deployer_machine() { + let scopes: Vec = AccountTier::Deployer.into(); + + assert_eq!(scopes, vec![Scope::Resources]); + } + } +} diff --git a/common/src/claims.rs b/common/src/claims.rs index 2a2d83669..c4e1b7e35 100644 --- a/common/src/claims.rs +++ b/common/src/claims.rs @@ -94,19 +94,7 @@ pub struct ScopeBuilder(Vec); impl ScopeBuilder { /// Create a builder with the standard scopes for new users. pub fn new() -> Self { - Self(vec![ - Scope::Deployment, - Scope::DeploymentPush, - Scope::Logs, - Scope::Service, - Scope::ServiceCreate, - Scope::Project, - Scope::ProjectCreate, - Scope::Resources, - Scope::ResourcesWrite, - Scope::Secret, - Scope::SecretWrite, - ]) + Self(Default::default()) } /// Extend the current scopes with admin scopes. @@ -123,6 +111,30 @@ impl ScopeBuilder { self } + /// Extend the current scopes with basic rights needed by a user. + pub fn with_basic(mut self) -> Self { + self.0.extend(vec![ + Scope::Deployment, + Scope::DeploymentPush, + Scope::Logs, + Scope::Service, + Scope::ServiceCreate, + Scope::Project, + Scope::ProjectCreate, + Scope::Resources, + Scope::ResourcesWrite, + Scope::Secret, + Scope::SecretWrite, + ]); + self + } + + /// Extend the current scopes with those needed by a deployer machine / user. + pub fn with_deploy_rights(mut self) -> Self { + self.0.extend(vec![Scope::Resources]); + self + } + pub fn build(self) -> Vec { self.0 }