api keys for everyone (#1811)

This commit is contained in:
Brandon Thomas
2026-07-27 14:55:54 -04:00
committed by GitHub
parent f708402e8a
commit 6ee5518c55
3 changed files with 5 additions and 41 deletions
@@ -10,7 +10,6 @@ use http_server_common::request::get_request_ip::get_request_ip;
use mysql_queries::queries::api_keys::insert_api_key::{insert_api_key, InsertApiKeyArgs};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags;
use crate::http_server::user_lookup::user_session::require_user_session::require_user_session;
use crate::state::server_state::ServerState;
use crate::util::generate_api_key::generate_api_key;
@@ -30,7 +29,6 @@ const MAX_DESCRIPTION_LEN: usize = 512;
(status = 200, body = CreateApiKeySuccessResponse),
(status = 400, body = CommonWebError),
(status = 401, body = CommonWebError),
(status = 403, body = CommonWebError),
(status = 500, body = CommonWebError),
),
)]
@@ -46,14 +44,6 @@ pub async fn create_api_key_handler(
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
// API key creation is gated behind the `api_key` feature flag.
let feature_flags = UserSessionFeatureFlags::new(user_session.maybe_feature_flags.as_deref());
if !feature_flags.can_create_api_key() {
warn!("User {:?} attempted to create an API key without the api_key feature flag", user_session.user_token.as_str());
return Err(CommonWebError::Forbidden);
}
let name = request.name.trim().to_string();
let maybe_description = request.maybe_description.as_ref().map(|s| s.trim().to_string());
@@ -101,10 +101,6 @@ impl UserSessionFeatureFlags {
pub fn can_use_referrals_program(&self) -> bool {
self.feature_flags.contains(&UserFeatureFlag::ReferralsProgram)
}
pub fn can_create_api_key(&self) -> bool {
self.feature_flags.contains(&UserFeatureFlag::ApiKey)
}
}
#[cfg(test)]
@@ -1,9 +1,8 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { Modal } from "@storyteller/ui-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faUser, faKey } from "@fortawesome/pro-solid-svg-icons";
import { Switch } from "@storyteller/ui-switch";
import { USER_FEATURE_FLAGS } from "@storyteller/api";
import { twMerge } from "tailwind-merge";
import { useEnterToGenerateStore } from "../../lib/enter-to-generate-store";
import { useLightboxSoundStore } from "../../lib/lightbox-sound-store";
@@ -18,41 +17,20 @@ interface SettingsModalProps {
onClose: () => void;
}
const BASE_TABS: { id: Tab; label: string; icon: typeof faCog }[] = [
const TABS: { id: Tab; label: string; icon: typeof faCog }[] = [
{ id: "general", label: "General", icon: faCog },
{ id: "account", label: "Account", icon: faUser },
{ id: "apiKeys", label: "API Keys", icon: faKey },
];
const API_KEYS_TAB: { id: Tab; label: string; icon: typeof faCog } = {
id: "apiKeys",
label: "API Keys",
icon: faKey,
};
export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
const { user } = useSession();
const [tab, setTab] = useState<Tab>("general");
const hasApiKeyFlag = !!user?.maybe_feature_flags?.includes(
USER_FEATURE_FLAGS.API_KEY,
);
const tabs = useMemo(
() => (hasApiKeyFlag ? [...BASE_TABS, API_KEYS_TAB] : BASE_TABS),
[hasApiKeyFlag],
);
useEffect(() => {
if (isOpen) setTab("general");
}, [isOpen]);
// If the only-conditional tab disappears (e.g. flag revoked while open),
// fall back to a visible tab.
useEffect(() => {
if (!tabs.some((t) => t.id === tab)) setTab("general");
}, [tabs, tab]);
const activeLabel = tabs.find((t) => t.id === tab)?.label ?? "";
const activeLabel = TABS.find((t) => t.id === tab)?.label ?? "";
return (
<Modal
@@ -69,7 +47,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
</div>
<hr className="my-2 hidden w-full border-ui-panel-border sm:block" />
<div className="flex gap-2 overflow-x-auto pe-10 sm:block sm:space-y-1 sm:overflow-visible sm:pe-0">
{tabs.map((t) => (
{TABS.map((t) => (
<button
key={t.id}
onClick={() => setTab(t.id)}