wasmer_deploy_schema/schema/
tokens.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::schema::WebcIdent;
5
6use uuid::Uuid;
7
8/// Basic JWT claims.
9///
10/// Only default fields.
11#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
12pub struct BaseClaims {
13    /// Expiration time.
14    #[serde(with = "time::serde::timestamp")]
15    pub exp: OffsetDateTime,
16
17    /// Issued at.
18    #[serde(with = "time::serde::timestamp")]
19    pub iat: OffsetDateTime,
20
21    /// Subject (aka user id)
22    pub sub: String,
23}
24
25/// Claims for a JWT token that allows running a specific workload on Deploy.
26#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, schemars::JsonSchema)]
27pub struct DeployWorkloadTokenV1 {
28    /// Expiration time.
29    #[serde(with = "time::serde::timestamp")]
30    #[schemars(with = "u64")]
31    pub exp: OffsetDateTime,
32
33    /// Issued at.
34    #[serde(with = "time::serde::timestamp")]
35    #[schemars(with = "u64")]
36    pub iat: OffsetDateTime,
37
38    /// Subject (aka user id)
39    pub sub: String,
40
41    /// jti (aka token id)
42    ///
43    /// This is a unique identifier for the token.
44    /// This can be optional
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub jti: Option<Uuid>,
47
48    /// A manually specified webc.
49    ///
50    /// Note that usually this will be empty, and provided via [`Self::cfg::webc`] instead,
51    /// since deployment configs are the common way to specify configurations.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub webc: Option<WebcIdent>,
54
55    /// Packages that this deployment is allowed to use.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub allowed_packages: Option<Vec<WebcIdent>>,
58}
59
60/// Claims for a JWT token that allows running a specific workload on Deploy.
61///
62/// Can represent different versions of the token.
63#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
64// untagged means try each variant, one by one.
65// Order is important! Newest versions should be on top.
66#[serde(untagged)]
67pub enum DeployWorkloadTokenData {
68    V1(DeployWorkloadTokenV1),
69}
70
71impl From<DeployWorkloadTokenV1> for DeployWorkloadTokenData {
72    fn from(value: DeployWorkloadTokenV1) -> Self {
73        Self::V1(value)
74    }
75}
76
77/// Claims for a JWT token that allows running a specific workload on Deploy.
78///
79/// Can represent different versions of the token.
80#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
81// untagged means try each variant, one by one.
82// Order is important! Newest versions should be on top.
83pub struct DeployWorkloadToken {
84    /// Raw JWT token.
85    pub raw: String,
86    /// Claims data extracted from the token.
87    pub data: DeployWorkloadTokenData,
88}
89
90impl DeployWorkloadTokenData {
91    pub fn expires(&self) -> Option<&OffsetDateTime> {
92        match self {
93            DeployWorkloadTokenData::V1(v1) => Some(&v1.exp),
94        }
95    }
96
97    pub fn issued_at(&self) -> &OffsetDateTime {
98        match self {
99            DeployWorkloadTokenData::V1(v1) => &v1.iat,
100        }
101    }
102
103    pub fn subject(&self) -> &str {
104        match self {
105            DeployWorkloadTokenData::V1(v1) => &v1.sub,
106        }
107    }
108
109    /// Direct webc spec for this workload.
110    ///
111    /// Note: do not confuse this witht he DeploymentConfig webc, which is
112    /// available on [`Self::cfg`].
113    pub fn webc_spec(&self) -> Option<&WebcIdent> {
114        match self {
115            Self::V1(v1) => v1.webc.as_ref(),
116        }
117    }
118
119    pub fn jti(&self) -> Option<&Uuid> {
120        match self {
121            Self::V1(v) => v.jti.as_ref(),
122        }
123    }
124
125    pub fn has_webc_spec(&self) -> bool {
126        self.webc_spec().is_some()
127    }
128
129    pub fn as_v1(&self) -> &DeployWorkloadTokenV1 {
130        match &self {
131            DeployWorkloadTokenData::V1(x) => x,
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_deser_tokens() {
142        #[allow(deprecated)]
143        let expected = DeployWorkloadTokenData::V1(DeployWorkloadTokenV1 {
144            exp: OffsetDateTime::from_unix_timestamp(10000).unwrap(),
145            iat: OffsetDateTime::from_unix_timestamp(20000).unwrap(),
146            sub: "user-1".to_string(),
147            jti: None,
148            webc: Some(WebcIdent {
149                repository: Some("https://registry.wapm.dev".parse().unwrap()),
150                namespace: "ns".to_string(),
151                name: "name".to_string(),
152                tag: Some("1.2.3".to_string()),
153            }),
154            allowed_packages: Some(vec![WebcIdent {
155                repository: Some("https://registry.wapm.dev".parse().unwrap()),
156                namespace: "ns".to_string(),
157                name: "name".to_string(),
158                tag: Some("1.2.3".to_string()),
159            }]),
160        });
161
162        let raw = r#"
163{
164  "exp": 10000,
165  "iat": 20000,
166  "sub": "user-1",
167  "webc": {
168    "repository": "https://registry.wapm.dev/",
169    "namespace": "ns",
170    "name": "name",
171    "tag": "1.2.3",
172    "hash": null,
173    "download_url": "https://test.com/lala"
174  },
175  "cfg": {
176    "uid": "123",
177    "backend_url": "http://test.com"
178  },
179  "allowed_packages": [
180    {
181      "repository": "https://registry.wapm.dev/",
182      "namespace": "ns",
183      "name": "name",
184      "tag": "1.2.3",
185      "hash": null,
186      "download_url": "https://test.com/lala"
187    }
188  ]
189}
190"#;
191
192        let deser: DeployWorkloadTokenData =
193            crate::schema::deserialize_json(raw.as_bytes()).unwrap();
194        assert_eq!(
195            deser
196                .webc_spec()
197                .unwrap()
198                .build_download_url()
199                .unwrap()
200                .to_string(),
201            "https://registry.wapm.dev/ns/name@1.2.3",
202        );
203        pretty_assertions::assert_eq!(deser, expected);
204    }
205}