Skip to main content

forest/rpc/methods/
auth.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::{
5    KeyStore,
6    auth::*,
7    lotus_json::lotus_json_with_self,
8    rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError},
9};
10use anyhow::Result;
11use chrono::Duration;
12use enumflags2::BitFlags;
13use fvm_ipld_blockstore::Blockstore;
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use serde_with::{DurationSeconds, serde_as};
17
18/// RPC call to create a new JWT Token
19pub enum AuthNew {}
20
21impl AuthNew {
22    pub fn create_token(
23        keystore: &KeyStore,
24        token_exp: Duration,
25        permissions: Vec<String>,
26    ) -> anyhow::Result<String> {
27        let ki = keystore.get(JWT_IDENTIFIER)?;
28        Ok(create_token(permissions, ki.private_key(), token_exp)?)
29    }
30}
31
32impl RpcMethod<2> for AuthNew {
33    const NAME: &'static str = "Filecoin.AuthNew";
34    const N_REQUIRED_PARAMS: usize = 1;
35    // Note: Lotus does not support the optional `expiration_secs` parameter
36    const PARAM_NAMES: [&'static str; 2] = ["permissions", "expiration_secs"];
37    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
38    const PERMISSION: Permission = Permission::Admin;
39    type Params = (Vec<String>, Option<i64>);
40    type Ok = Vec<u8>;
41    async fn handle(
42        ctx: Ctx<impl Blockstore>,
43        (permissions, expiration_secs): Self::Params,
44        _: &http::Extensions,
45    ) -> Result<Self::Ok, ServerError> {
46        let ks = ctx.keystore.read();
47        // Lotus admin tokens do not expire but Forest requires all JWT tokens to
48        // have an expiration date. So we set the expiration date to 100 years in
49        // the future to match user-visible behavior of Lotus.
50        let token_exp = expiration_secs
51            .map(chrono::Duration::seconds)
52            .unwrap_or_else(|| chrono::Duration::days(365 * 100));
53        let token = Self::create_token(&ks, token_exp, permissions)?;
54        Ok(token.as_bytes().to_vec())
55    }
56}
57
58pub enum AuthVerify {}
59impl RpcMethod<1> for AuthVerify {
60    const NAME: &'static str = "Filecoin.AuthVerify";
61    const PARAM_NAMES: [&'static str; 1] = ["header_raw"];
62    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
63    const PERMISSION: Permission = Permission::Read;
64    type Params = (String,);
65    type Ok = Vec<String>;
66    async fn handle(
67        ctx: Ctx<impl Blockstore>,
68        (header_raw,): Self::Params,
69        _: &http::Extensions,
70    ) -> Result<Self::Ok, ServerError> {
71        let ks = ctx.keystore.read();
72        let token = header_raw.trim_start_matches("Bearer ");
73        let ki = ks.get(JWT_IDENTIFIER)?;
74        let perms = verify_token(token, ki.private_key())?;
75        Ok(perms)
76    }
77}
78
79#[serde_as]
80#[derive(Clone, Deserialize, Serialize, JsonSchema)]
81pub struct AuthNewParams {
82    pub perms: Vec<String>,
83    #[serde_as(as = "DurationSeconds<i64>")]
84    #[schemars(with = "i64")]
85    pub token_exp: Duration,
86}
87lotus_json_with_self!(AuthNewParams);
88
89impl AuthNewParams {
90    pub fn process_perms(perm: String) -> Result<Vec<String>, ServerError> {
91        Ok(match perm.to_lowercase().as_str() {
92            "admin" => ADMIN,
93            "sign" => SIGN,
94            "write" => WRITE,
95            "read" => READ,
96            _ => return Err(ServerError::invalid_params("unknown permission", None)),
97        }
98        .iter()
99        .map(ToString::to_string)
100        .collect())
101    }
102}
103
104impl From<AuthNewParams> for (Vec<String>, Option<i64>) {
105    fn from(value: AuthNewParams) -> Self {
106        (value.perms, Some(value.token_exp.num_seconds()))
107    }
108}