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::{Context as _, 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 = match expiration_secs {
52            Some(secs) => Duration::try_seconds(secs)
53                .with_context(|| format!("expirationSecs out of range: {secs}"))?,
54            None => Duration::days(365 * 100),
55        };
56        let token = Self::create_token(&ks, token_exp, permissions)?;
57        Ok(token.as_bytes().to_vec())
58    }
59}
60
61pub enum AuthVerify {}
62impl RpcMethod<1> for AuthVerify {
63    const NAME: &'static str = "Filecoin.AuthVerify";
64    const PARAM_NAMES: [&'static str; 1] = ["token"];
65    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
66    const PERMISSION: Permission = Permission::Read;
67    const DESCRIPTION: &'static str =
68        "Verifies a JWT authentication token and returns its permissions.";
69    type Params = (String,);
70    type Ok = Vec<String>;
71    async fn handle(
72        ctx: Ctx,
73        (token,): Self::Params,
74        _: &http::Extensions,
75    ) -> Result<Self::Ok, ServerError> {
76        let ks = ctx.keystore.read();
77        let ki = ks.get(JWT_IDENTIFIER)?;
78        let perms = verify_token(&token, ki.private_key())?;
79        Ok(perms)
80    }
81}
82
83#[serde_as]
84#[derive(Clone, Deserialize, Serialize, JsonSchema)]
85pub struct AuthNewParams {
86    pub perms: Vec<String>,
87    #[serde_as(as = "DurationSeconds<i64>")]
88    #[schemars(with = "i64")]
89    pub token_exp: Duration,
90}
91lotus_json_with_self!(AuthNewParams);
92
93impl AuthNewParams {
94    pub fn process_perms(perm: String) -> Result<Vec<String>, ServerError> {
95        Ok(match perm.to_lowercase().as_str() {
96            "admin" => ADMIN,
97            "sign" => SIGN,
98            "write" => WRITE,
99            "read" => READ,
100            _ => return Err(ServerError::invalid_params("unknown permission", None)),
101        }
102        .iter()
103        .map(ToString::to_string)
104        .collect())
105    }
106}
107
108impl From<AuthNewParams> for (Vec<String>, Option<i64>) {
109    fn from(value: AuthNewParams) -> Self {
110        (value.perms, Some(value.token_exp.num_seconds()))
111    }
112}