Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
a71b602
Add Zendesk as an OAuth backend
augustuswm Apr 28, 2026
c1988ab
Add experimental crate for cli helpers
augustuswm Apr 29, 2026
7f99622
OAuth code proxy work
augustuswm Apr 29, 2026
eea958f
Fmt and lint
augustuswm Apr 30, 2026
3ada182
Lint and fmt
augustuswm Apr 30, 2026
cd8d1ee
A lot of refactoring of OAuth internals
augustuswm May 5, 2026
a5f6aec
Rough cut
augustuswm May 6, 2026
09711f5
Fmt
augustuswm May 6, 2026
8fea7ea
Clippy lints
augustuswm May 6, 2026
3aeddc4
Redirect fixes
augustuswm May 6, 2026
532feac
Filter proxy responses
augustuswm May 6, 2026
aa36bb1
More pkce support
augustuswm May 6, 2026
d4c6992
Early return on invalid scope
augustuswm May 6, 2026
5d94c95
Add check on provider
augustuswm May 6, 2026
b87a944
Error fixes
augustuswm May 6, 2026
77383ee
Adding redirect url validation
augustuswm May 6, 2026
014ea59
Validate pkce challenge
augustuswm May 6, 2026
12abe10
Handle idp user info errors
augustuswm May 6, 2026
0a5c7d5
Skip serialize on secret
augustuswm May 6, 2026
0f903fc
Cookie scoping
augustuswm May 6, 2026
68f7f3c
Fixes for login attempt state transitions
augustuswm May 6, 2026
418126d
Fmt
augustuswm May 6, 2026
1b284c2
More clippy fixes
augustuswm May 6, 2026
bf412d0
Fmt
augustuswm May 6, 2026
91b18c3
More spec compliance
augustuswm May 6, 2026
6efc48b
Enfore permission
augustuswm May 7, 2026
61646b1
Fmt
augustuswm May 7, 2026
a0ba072
Merge branch 'main' into oauth-rework
augustuswm May 7, 2026
e7de8f1
Merge fixes
augustuswm May 7, 2026
1733c8d
Fmt
augustuswm May 7, 2026
a539ffe
Remove extraneous dep
augustuswm May 7, 2026
0f9b4b3
Update v-cli-sdk/src/cmd/auth/oauth/code.rs
augustuswm May 7, 2026
29f2c7a
Update v-api/src/endpoints/login/oauth/remote/mod.rs
augustuswm May 7, 2026
e984b91
Update v-cli-sdk/src/cmd/auth/oauth/device.rs
augustuswm May 7, 2026
16257bb
Update v-api/src/endpoints/login/oauth/flow/mod.rs
augustuswm May 7, 2026
a1ce9ed
Fix local dev endpoints
augustuswm May 7, 2026
2c22bc0
Pass down provider
augustuswm May 7, 2026
450ade6
Fmt
augustuswm May 7, 2026
1a7b798
Permissions need to be resolved during login to determine idp token a…
augustuswm May 7, 2026
0e8bb73
Add support for Zendesk expires_in extension
augustuswm May 7, 2026
9dbfee4
More changes to align with spec
augustuswm May 7, 2026
2115ca4
Verify provider
augustuswm May 7, 2026
a20ef12
Fix for race with redirect uris
augustuswm May 7, 2026
8306f45
Version bump
augustuswm May 7, 2026
4bc588e
Remove percent-encoding
augustuswm May 7, 2026
59bc8a5
Merge branch 'main' into oauth-rework
augustuswm May 7, 2026
9b7d7db
Fix new provider info
augustuswm May 7, 2026
d0842df
Clean up from main merge. Fix config error consumed. Update state tra…
augustuswm May 7, 2026
09f4eed
One more error propagation fix
augustuswm May 7, 2026
51fb286
More cleanup and notes
augustuswm May 7, 2026
2d26489
Use trait mapping to HttpError
augustuswm May 7, 2026
1f26a14
Fix for revocation when user has not requested access to a token
augustuswm May 7, 2026
af5c72c
Fix incorrect debug message
augustuswm May 7, 2026
4f892aa
Revert redirect uri checking
augustuswm May 7, 2026
b017cdd
Larger rework of device authentication
augustuswm May 8, 2026
dcfdda2
Cleaning up oauth info
augustuswm May 8, 2026
ca3e196
More work on device flow
augustuswm May 15, 2026
50b934e
Merge branch 'main' into oauth-rework
augustuswm May 15, 2026
66e9fa6
Restrict permissions put added via API keys even though they are inert
augustuswm May 15, 2026
bdf5c31
Remove dep
augustuswm May 15, 2026
bc256e8
Add suppor for ephemeral mappers
augustuswm May 18, 2026
5cd2e43
Add API indicator for ephemeral mappers
augustuswm May 18, 2026
1f4fdce
Merge branch 'ephemeral-mappers' into oauth-rework
augustuswm May 18, 2026
1feae7b
Remove extraneous reference
augustuswm May 18, 2026
63e6acc
Nit
augustuswm May 18, 2026
9cfcc54
Cleanup user count branch checks
augustuswm May 18, 2026
83d83eb
Typo fixgs
augustuswm May 18, 2026
ee516e1
Merge branch 'ephemeral-mappers' into oauth-rework
augustuswm May 19, 2026
1eee1ea
Fix device token expiration check
augustuswm May 19, 2026
1d0fcf0
Validate nbf claim on tokens
augustuswm May 19, 2026
7fcdb10
Verify crc returns values from Google
augustuswm May 19, 2026
eb75be0
Lower redirect uri checking to context
augustuswm May 19, 2026
4b89726
Drop authz codes
augustuswm May 19, 2026
e0fdba6
Remove pkce arg from device flow
augustuswm May 19, 2026
5a9a964
Verify csrf in cli code flow
augustuswm May 19, 2026
8e80d6e
Fix renamed command
augustuswm May 19, 2026
212e2dd
Refactor redirect uri parsing
augustuswm May 19, 2026
ae4f038
Move migration file dates
augustuswm May 19, 2026
509bb96
Simplify OAuth error construction
augustuswm May 19, 2026
ba25e90
Use typed urls in device flow
augustuswm May 19, 2026
315af1a
Lint
augustuswm May 19, 2026
4cd4a80
Fix signed type in response
augustuswm May 19, 2026
7ecb849
Fix copy+paste
augustuswm May 19, 2026
ee8d1bc
Fix outdated comment
augustuswm May 19, 2026
4d36818
Extract code challenge function and add tests
augustuswm May 20, 2026
c79c043
Decode query params in proxy server with serde_urlencoded
augustuswm May 20, 2026
5577154
Fix error code todo
augustuswm May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion Cargo.lock

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

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"v-api-installer",
"v-api-param",
"v-api-permission-derive",
"v-cli-sdk",
"v-model",
"xtask"
]
Expand Down Expand Up @@ -34,14 +35,16 @@ hex = "0.4.3"
http = "1"
http-body-util = "0.1.3"
hyper = "1.9.0"
hyper-util = "0.1"
jsonwebtoken = { version = "10.2", features = ["aws_lc_rs"] }
mockall = "0.14.0"
newtype-uuid = { version = "1.3.2", features = ["schemars08", "serde", "v4"] }
oauth2 = { version = "5.0.0", default-features = false }
oauth2-reqwest = "0.1.0-alpha.3"
owo-colors = "4.2.3"
partial-struct = { git = "https://github.com/oxidecomputer/partial-struct" }
percent-encoding = "2.3.2"
proc-macro2 = "1"
progenitor-client = "0.14.0"
quote = "1"
rand = "0.10.1"
rand_core = "0.10.1"
Expand All @@ -58,6 +61,7 @@ sha2 = "0.11.0"
slog = "2.8.2"
steno = { git = "https://github.com/oxidecomputer/steno" }
syn = "2"
tabwriter = "1.4.1"
tap = "1.0.1"
tempfile = "3"
thiserror = "2"
Expand Down
2 changes: 2 additions & 0 deletions v-api-permission-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ fn from_system_permission_tokens(
VPermission::ManageMagicLinkClientsAll => Self::ManageMagicLinkClientsAll,

VPermission::CreateAccessToken => Self::CreateAccessToken,
VPermission::RetrieveRemoteAccessToken => Self::RetrieveRemoteAccessToken,

#saga_permission_from_tokens

Expand Down Expand Up @@ -852,6 +853,7 @@ fn system_permission_tokens() -> TokenStream {
ManageMagicLinkClientsAll,

CreateAccessToken,
RetrieveRemoteAccessToken,

#saga_permission_tokens

Expand Down
3 changes: 1 addition & 2 deletions v-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ oauth2 = { workspace = true }
oauth2-reqwest = { workspace = true }
newtype-uuid = { workspace = true }
partial-struct = { workspace = true }
percent-encoding = { workspace = true }
rand = { workspace = true, features = ["std"] }
reqwest = { workspace = true }
rsa = { workspace = true, features = ["sha2"] }
Expand All @@ -46,7 +45,7 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tracing = { workspace = true }
url = { workspace = true }
uuid = { workspace = true, features = ["v4", "serde"] }
uuid = { workspace = true, features = ["v4", "v5", "serde"] }
v-api-param = { path = "../v-api-param" }
v-api-permission-derive = { path = "../v-api-permission-derive" }
v-model = { path = "../v-model" }
Expand Down
1 change: 1 addition & 0 deletions v-api/src/authn/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ where
let mut validation = Validation::new(algorithm?);
validation.set_audience(&[ctx.public_url()]);
validation.set_issuer(&[ctx.public_url()]);
validation.validate_nbf = true;

let data = decode(token, &key, &validation).map_err(JwtError::Decode)?;

Expand Down
25 changes: 22 additions & 3 deletions v-api/src/authn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ pub enum CloudKmsError {
ClientError(#[from] google_cloudkms1::Error),
#[error("Signature received failed CRC check")]
CorruptedSignature,
#[error("CloudKMS did not verify the digest CRC32C sent with the request")]
DigestCrc32cNotVerified,
#[error("Failed to decode signature: {0}")]
FailedToDecodeSignature(#[from] base64::DecodeError),
#[error("Failed to deserialize response: {0}")]
Expand Down Expand Up @@ -280,9 +282,26 @@ impl Signer {
let signature = match response {
Ok((_, response)) => {
tracing::info!("Library deserialization succeeded");
response
.signature
.ok_or_else(|| Box::new(CloudKmsError::MissingSignature))

// Google should always be verifying our input
if response.verified_digest_crc32c != Some(true) {
Err(Box::new(CloudKmsError::DigestCrc32cNotVerified))
} else {
let signature = response
.signature
.ok_or_else(|| Box::new(CloudKmsError::MissingSignature))?;

// Verify the CRC32C of the returned signature to detect corruption
// in transit
if let Some(expected_crc) = response.signature_crc32c {
let actual_crc = crc32c(&signature);
if actual_crc as i64 != expected_crc {
return Err(Box::new(CloudKmsError::CorruptedSignature).into());
}
}

Ok(signature)
}
}
Err(google_cloudkms1::Error::JsonDecodeError(body, _)) => {
tracing::info!("Using fallback deserialization");
Expand Down
108 changes: 97 additions & 11 deletions v-api/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ use jsonwebtoken::jwk::{
AlgorithmParameters, CommonParameters, Jwk, KeyAlgorithm, PublicKeyUse, RSAKeyParameters,
RSAKeyType,
};
use newtype_uuid::TypedUuid;
use partial_struct::partial;
use rsa::{
RsaPrivateKey, RsaPublicKey,
pkcs1v15::{SigningKey, VerifyingKey},
pkcs8::{DecodePrivateKey, DecodePublicKey},
traits::PublicKeyParts,
};
use secrecy::ExposeSecret;
use secrecy::{ExposeSecret, SecretString};
use serde::{
Deserialize, Deserializer,
de::{self, Visitor},
};
use std::path::PathBuf;
use thiserror::Error;
use v_api_param::StringParam;
use v_api_param::{ParamResolutionError, StringParam};
use v_model::OAuthClientId;

use crate::{
authn::{
Expand Down Expand Up @@ -151,25 +154,108 @@ pub struct SendGridConfig {
pub struct OAuthProviders {
pub github: Option<OAuthConfig>,
pub google: Option<OAuthConfig>,
pub zendesk: Option<OAuthConfig>,
}

#[derive(Debug, Deserialize)]
#[partial(ResolvedOAuthConfig)]
#[derive(Clone, Debug, Deserialize)]
pub struct OAuthConfig {
pub device: OAuthDeviceConfig,
pub web: OAuthWebConfig,
#[partial(ResolvedOAuthConfig(retype = Option<ResolvedOAuthDeviceConfig>))]
pub device: Option<OAuthDeviceConfig>,
#[partial(ResolvedOAuthConfig(retype = Option<ResolvedOAuthWebConfig>))]
pub web: Option<OAuthWebConfig>,
#[partial(ResolvedOAuthConfig(retype = Option<ResolvedOAuthWebProxyConfig>))]
pub proxy_web: Option<OAuthWebProxyConfig>,
}

#[derive(Debug, Deserialize)]
#[partial(ResolvedOAuthDeviceConfig)]
#[derive(Clone, Debug, Deserialize)]
pub struct OAuthDeviceConfig {
pub client_id: String,
pub client_secret: StringParam,
pub client_id: TypedUuid<OAuthClientId>,
pub remote_client_id: String,
#[partial(ResolvedOAuthDeviceConfig(retype = SecretString))]
pub remote_client_secret: StringParam,
}

#[derive(Debug, Deserialize)]
#[partial(ResolvedOAuthWebConfig)]
#[derive(Clone, Debug, Deserialize)]
pub struct OAuthWebConfig {
pub client_id: String,
pub client_secret: StringParam,
pub remote_client_id: String,
#[partial(ResolvedOAuthWebConfig(retype = SecretString))]
pub remote_client_secret: StringParam,
}

#[partial(ResolvedOAuthWebProxyConfig)]
#[derive(Clone, Debug, Deserialize)]
pub struct OAuthWebProxyConfig {
pub client_id: TypedUuid<OAuthClientId>,
pub redirect_uri: String,
pub proxy_port: u16,
}

impl OAuthConfig {
pub fn resolve(
&self,
base: Option<PathBuf>,
) -> Result<ResolvedOAuthConfig, ParamResolutionError> {
let device = self
.device
.as_ref()
.map(|d| d.resolve(base.clone()))
.transpose()?;
let web = self
.web
.as_ref()
.map(|w| w.resolve(base.clone()))
.transpose()?;
let proxy_web = self
.proxy_web
.as_ref()
.map(|p| p.resolve(base))
.transpose()?;
Ok(ResolvedOAuthConfig {
device,
web,
proxy_web,
})
}
}
impl OAuthDeviceConfig {
pub fn resolve(
&self,
base: Option<PathBuf>,
) -> Result<ResolvedOAuthDeviceConfig, ParamResolutionError> {
let remote_client_secret = self.remote_client_secret.resolve(base)?;
Ok(ResolvedOAuthDeviceConfig {
client_id: self.client_id,
remote_client_id: self.remote_client_id.clone(),
remote_client_secret,
})
}
}
impl OAuthWebConfig {
pub fn resolve(
&self,
base: Option<PathBuf>,
) -> Result<ResolvedOAuthWebConfig, ParamResolutionError> {
let remote_client_secret = self.remote_client_secret.resolve(base)?;
Ok(ResolvedOAuthWebConfig {
remote_client_id: self.remote_client_id.clone(),
remote_client_secret,
})
}
}
impl OAuthWebProxyConfig {
pub fn resolve(
&self,
_base: Option<PathBuf>,
) -> Result<ResolvedOAuthWebProxyConfig, ParamResolutionError> {
Ok(ResolvedOAuthWebProxyConfig {
client_id: self.client_id,
redirect_uri: self.redirect_uri.clone(),
proxy_port: self.proxy_port,
})
}
}

impl AsymmetricKey {
Expand Down
Loading
Loading