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