Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: return 404 instead of 401 for missing OAuth2 apps#18755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ThomasK33 merged 1 commit intomainfromfix/oauth2-app-deletion-logout
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions.claude/scripts/format.sh
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,13 +6,87 @@

set -euo pipefail

# A variable to memoize the command for canonicalizing paths.
_CANONICALIZE_CMD=""

# canonicalize_path resolves a path to its absolute, canonical form.
# It tries 'realpath' and 'readlink -f' in order.
# The chosen command is memoized to avoid repeated checks.
# If none of these are available, it returns an empty string.
canonicalize_path() {
local path_to_resolve="$1"

# If we haven't determined a command yet, find one.
if [[ -z "$_CANONICALIZE_CMD" ]]; then
if command -v realpath >/dev/null 2>&1; then
_CANONICALIZE_CMD="realpath"
elif command -v readlink >/dev/null 2>&1 && readlink -f . >/dev/null 2>&1; then
_CANONICALIZE_CMD="readlink"
else
# No command found, so we can't resolve.
# We set a "none" value to prevent re-checking.
_CANONICALIZE_CMD="none"
fi
fi

# Now, execute the command.
case "$_CANONICALIZE_CMD" in
realpath)
realpath "$path_to_resolve" 2>/dev/null
;;
readlink)
readlink -f "$path_to_resolve" 2>/dev/null
;;
*)
# This handles the "none" case or any unexpected error.
echo ""
;;
esac
}

# Read JSON input from stdin
input=$(cat)

# Extract the file path from the JSON input
# Expected format: {"tool_input": {"file_path": "/absolute/path/to/file"}} or {"tool_response": {"filePath": "/absolute/path/to/file"}}
file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_response.filePath // empty')

# Secure path canonicalization to prevent path traversal attacks
# Resolve repo root to an absolute, canonical path.
repo_root_raw="$(cd "$(dirname "$0")/../.." && pwd)"
repo_root="$(canonicalize_path "$repo_root_raw")"
if [[ -z "$repo_root" ]]; then
# Fallback if canonicalization fails
repo_root="$repo_root_raw"
fi

# Resolve the input path to an absolute path
if [[ "$file_path" = /* ]]; then
# Already absolute
abs_file_path="$file_path"
else
# Make relative paths absolute from repo root
abs_file_path="$repo_root/$file_path"
fi

# Canonicalize the path (resolve symlinks and ".." segments)
canonical_file_path="$(canonicalize_path "$abs_file_path")"

# Check if canonicalization failed or if the resolved path is outside the repo
if [[ -z "$canonical_file_path" ]] || { [[ "$canonical_file_path" != "$repo_root" ]] && [[ "$canonical_file_path" != "$repo_root"/* ]]; }; then
echo "Error: File path is outside repository or invalid: $file_path" >&2
exit 1
fi

# Handle the case where the file path is the repository root itself.
if [[ "$canonical_file_path" == "$repo_root" ]]; then
echo "Warning: Formatting the repository root is not a supported operation. Skipping." >&2
exit 0
fi

# Convert back to relative path from repo root for consistency
file_path="${canonical_file_path#"$repo_root"/}"

if [[ -z "$file_path" ]]; then
echo "Error: No file path provided in input" >&2
exit 1
Expand Down
70 changes: 35 additions & 35 deletions.mcp.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
{
"mcpServers": {
"go-language-server": {
"type":"stdio",
"command":"go",
"args": [
"run",
"github.com/isaacphi/mcp-language-server@latest",
"-workspace",
"./",
"-lsp",
"go",
"--",
"run",
"golang.org/x/tools/gopls@latest"
],
"env": {}
},
"typescript-language-server": {
"type":"stdio",
"command":"go",
"args": [
"run",
"github.com/isaacphi/mcp-language-server@latest",
"-workspace",
"./site/",
"-lsp",
"pnpx",
"--",
"typescript-language-server",
"--stdio"
],
"env": {}
}
}
}
"mcpServers": {
"go-language-server": {
"type":"stdio",
"command":"go",
"args": [
"run",
"github.com/isaacphi/mcp-language-server@latest",
"-workspace",
"./",
"-lsp",
"go",
"--",
"run",
"golang.org/x/tools/gopls@latest"
],
"env": {}
},
"typescript-language-server": {
"type":"stdio",
"command":"go",
"args": [
"run",
"github.com/isaacphi/mcp-language-server@latest",
"-workspace",
"./site/",
"-lsp",
"pnpx",
"--",
"typescript-language-server",
"--stdio"
],
"env": {}
}
}
}
16 changes: 10 additions & 6 deletionscoderd/httpmw/oauth2.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -225,7 +225,7 @@ func ExtractOAuth2ProviderAppWithOAuth2Errors(db database.Store) func(http.Handl
typeerrorWriterinterface {
writeMissingClientID(ctx context.Context,rw http.ResponseWriter)
writeInvalidClientID(ctx context.Context,rw http.ResponseWriter,errerror)
writeInvalidClient(ctx context.Context,rw http.ResponseWriter)
writeClientNotFound(ctx context.Context,rw http.ResponseWriter)
}

// codersdkErrorWriter writes standard codersdk errors for general API endpoints
Expand All@@ -244,9 +244,13 @@ func (*codersdkErrorWriter) writeInvalidClientID(ctx context.Context, rw http.Re
})
}

func (*codersdkErrorWriter)writeInvalidClient(ctx context.Context,rw http.ResponseWriter) {
httpapi.Write(ctx,rw,http.StatusUnauthorized, codersdk.Response{
Message:"Invalid OAuth2 client.",
func (*codersdkErrorWriter)writeClientNotFound(ctx context.Context,rw http.ResponseWriter) {
// Management API endpoints return 404 for missing OAuth2 apps (proper REST semantics).
// This differs from OAuth2 protocol endpoints which return 401 "invalid_client" per RFC 6749.
// Returning 401 here would trigger the frontend's automatic logout interceptor when React Query
// refetches a deleted app, incorrectly logging out users who just deleted their own OAuth2 apps.
httpapi.Write(ctx,rw,http.StatusNotFound, codersdk.Response{
Message:"OAuth2 application not found.",
})
}

Expand All@@ -261,7 +265,7 @@ func (*oauth2ErrorWriter) writeInvalidClientID(ctx context.Context, rw http.Resp
httpapi.WriteOAuth2Error(ctx,rw,http.StatusUnauthorized,"invalid_client","The client credentials are invalid")
}

func (*oauth2ErrorWriter)writeInvalidClient(ctx context.Context,rw http.ResponseWriter) {
func (*oauth2ErrorWriter)writeClientNotFound(ctx context.Context,rw http.ResponseWriter) {
httpapi.WriteOAuth2Error(ctx,rw,http.StatusUnauthorized,"invalid_client","The client credentials are invalid")
}

Expand DownExpand Up@@ -308,7 +312,7 @@ func extractOAuth2ProviderAppBase(db database.Store, errWriter errorWriter) func

app,err:=db.GetOAuth2ProviderAppByID(ctx,appID)
ifhttpapi.Is404Error(err) {
errWriter.writeInvalidClient(ctx,rw)
errWriter.writeClientNotFound(ctx,rw)
return
}
iferr!=nil {
Expand Down
7 changes: 7 additions & 0 deletionssite/src/modules/dashboard/DashboardProvider.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
import{appearance}from"api/queries/appearance";
import{buildInfo}from"api/queries/buildInfo";
import{entitlements}from"api/queries/entitlements";
import{experiments}from"api/queries/experiments";
import{organizations}from"api/queries/organizations";
importtype{
AppearanceConfig,
BuildInfoResponse,
Entitlements,
Experiment,
Organization,
Expand All@@ -21,6 +23,7 @@ export interface DashboardValue {
entitlements:Entitlements;
experiments:Experiment[];
appearance:AppearanceConfig;
buildInfo:BuildInfoResponse;
organizations:readonlyOrganization[];
showOrganizations:boolean;
canViewOrganizationSettings:boolean;
Expand All@@ -36,12 +39,14 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
constentitlementsQuery=useQuery(entitlements(metadata.entitlements));
constexperimentsQuery=useQuery(experiments(metadata.experiments));
constappearanceQuery=useQuery(appearance(metadata.appearance));
constbuildInfoQuery=useQuery(buildInfo(metadata["build-info"]));
constorganizationsQuery=useQuery(organizations());

consterror=
entitlementsQuery.error||
appearanceQuery.error||
experimentsQuery.error||
buildInfoQuery.error||
organizationsQuery.error;

if(error){
Expand All@@ -52,6 +57,7 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
!entitlementsQuery.data||
!appearanceQuery.data||
!experimentsQuery.data||
!buildInfoQuery.data||
!organizationsQuery.data;

if(isLoading){
Expand All@@ -70,6 +76,7 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
entitlements:entitlementsQuery.data,
experiments:experimentsQuery.data,
appearance:appearanceQuery.data,
buildInfo:buildInfoQuery.data,
organizations:organizationsQuery.data,
showOrganizations,
canViewOrganizationSettings:
Expand Down
5 changes: 4 additions & 1 deletionsite/src/modules/management/DeploymentSidebar.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,8 @@ import { DeploymentSidebarView } from "./DeploymentSidebarView";
*/
exportconstDeploymentSidebar:FC=()=>{
const{ permissions}=useAuthenticated();
const{ entitlements, showOrganizations}=useDashboard();
const{ entitlements, showOrganizations, experiments, buildInfo}=
useDashboard();
consthasPremiumLicense=
entitlements.features.multiple_organizations.enabled;

Expand All@@ -17,6 +18,8 @@ export const DeploymentSidebar: FC = () => {
permissions={permissions}
showOrganizations={showOrganizations}
hasPremiumLicense={hasPremiumLicense}
experiments={experiments}
buildInfo={buildInfo}
/>
);
};
16 changes: 12 additions & 4 deletionssite/src/modules/management/DeploymentSidebarView.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
importtype{BuildInfoResponse,Experiment}from"api/typesGenerated";
import{
SidebarasBaseSidebar,
SettingsSidebarNavItemasSidebarNavItem,
Expand All@@ -6,12 +7,15 @@ import { Stack } from "components/Stack/Stack";
import{ArrowUpRight}from"lucide-react";
importtype{Permissions}from"modules/permissions";
importtype{FC}from"react";
import{isDevBuild}from"utils/buildInfo";

interfaceDeploymentSidebarViewProps{
/** Site-wide permissions. */
permissions:Permissions;
showOrganizations:boolean;
hasPremiumLicense:boolean;
experiments:Experiment[];
buildInfo:BuildInfoResponse;
}

/**
Expand All@@ -22,6 +26,8 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
permissions,
showOrganizations,
hasPremiumLicense,
experiments,
buildInfo,
})=>{
return(
<BaseSidebar>
Expand All@@ -47,10 +53,12 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
External Authentication
</SidebarNavItem>
)}
{/* Not exposing this yet since token exchange is not finished yet.
<SidebarNavItem href="oauth2-provider/apps">
OAuth2 Applications
</SidebarNavItem>*/}
{permissions.viewDeploymentConfig&&
(experiments.includes("oauth2")||isDevBuild(buildInfo))&&(
<SidebarNavItemhref="/deployment/oauth2-provider/apps">
OAuth2 Applications
</SidebarNavItem>
)}
{permissions.viewDeploymentConfig&&(
<SidebarNavItemhref="/deployment/network">Network</SidebarNavItem>
)}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,7 @@ export const EditOAuth2AppPageView: FC<EditOAuth2AppProps> = ({
confirmLoading={mutatingResource.deleteApp}
name={app.name}
entity="OAuth2 application"
info="Deleting this OAuth2 application will immediately invalidate all active sessions and API keys associated with it. Users currently authenticated through this application will be logged out and need to re-authenticate."
onConfirm={()=>deleteApp(app.name)}
onCancel={()=>setShowDelete(false)}
/>
Expand Down
9 changes: 8 additions & 1 deletionsite/src/pages/UserSettingsPage/Sidebar.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,17 +13,19 @@ import {
FingerprintIcon,
KeyIcon,
LockIcon,
ShieldIcon,
UserIcon,
}from"lucide-react";
import{useDashboard}from"modules/dashboard/useDashboard";
importtype{FC}from"react";
import{isDevBuild}from"utils/buildInfo";

interfaceSidebarProps{
user:User;
}

exportconstSidebar:FC<SidebarProps>=({ user})=>{
const{ entitlements}=useDashboard();
const{ entitlements, experiments, buildInfo}=useDashboard();
constshowSchedulePage=
entitlements.features.advanced_template_scheduling.enabled;

Expand All@@ -43,6 +45,11 @@ export const Sidebar: FC<SidebarProps> = ({ user }) => {
<SidebarNavItemhref="external-auth"icon={GitIcon}>
External Authentication
</SidebarNavItem>
{(experiments.includes("oauth2")||isDevBuild(buildInfo))&&(
<SidebarNavItemhref="oauth2-provider"icon={ShieldIcon}>
OAuth2 Applications
</SidebarNavItem>
)}
{showSchedulePage&&(
<SidebarNavItemhref="schedule"icon={CalendarCogIcon}>
Schedule
Expand Down
5 changes: 5 additions & 0 deletionssite/src/pages/WorkspacePage/WorkspacePage.test.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,7 @@ import type { FC } from "react";
import{typeLocation,useLocation}from"react-router-dom";
import{
MockAppearanceConfig,
MockBuildInfo,
MockDeploymentConfig,
MockEntitlements,
MockFailedWorkspace,
Expand DownExpand Up@@ -554,6 +555,10 @@ describe("WorkspacePage", () => {
appearance:MockAppearanceConfig,
entitlements:MockEntitlements,
experiments:[],
buildInfo:{
...MockBuildInfo,
version:"v0.0.0-test",
},
organizations:[MockOrganization],
showOrganizations:true,
canViewOrganizationSettings:true,
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp