Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
701 changes: 701 additions & 0 deletions docs/superpowers/plans/2026-08-06-device-auth-decouple.md

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions packages/common/src/dto/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,29 @@ export type NewSshKey = Omit<SshKey, 'creationTimestamp'> & {
key: string;
};

export type DeviceAuthToken = {
name: string;
provider?: string;
/** ISO 8601 timestamp string as returned by Kubernetes and serialized by Fastify */
creationTimestamp?: string;
/** Token validity from a separate validate call. 'unknown' means check could not complete. */
valid?: 'valid' | 'invalid' | 'unknown';
};
Comment thread
olexii4 marked this conversation as resolved.

export type DeviceCodeResponse = {
deviceCode: string;
userCode: string;
verificationUri: string;
interval: number;
};

export type DeviceAuthPollResult =
| { status: 'pending' }
| { status: 'slow_down' }
| { status: 'authorized'; token: DeviceAuthToken }
| { status: 'expired' }
| { status: 'error'; message: string };

export interface IPatch {
op: string;
path: string;
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/dto/cluster-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export interface ClusterConfig {
allWorkspacesLimit: number;
runningWorkspacesLimit: number;
currentArchitecture?: Architecture;
githubDeviceAuthEnabled: boolean;
}
3 changes: 3 additions & 0 deletions packages/dashboard-backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { registerBackupRoutes } from '@/routes/api/backup';
import { registerClusterConfigRoute } from '@/routes/api/clusterConfig';
import { registerClusterInfoRoute } from '@/routes/api/clusterInfo';
import { registerDataResolverRoute } from '@/routes/api/dataResolver';
import { registerDeviceAuthTokenRoutes } from '@/routes/api/deviceAuthToken';
import { registerDevWorkspaceClusterRoutes } from '@/routes/api/devworkspaceCluster';
import { registerDevworkspaceResourcesRoute } from '@/routes/api/devworkspaceResources';
import { registerDevworkspacesRoutes } from '@/routes/api/devworkspaces';
Expand Down Expand Up @@ -149,5 +150,7 @@ export default async function buildApp(server: FastifyInstance): Promise<unknown
registerAiConfigRoutes(server),

registerAiRegistryRoute(isLocalRun(), server),

registerDeviceAuthTokenRoutes(server),
]);
}
44 changes: 44 additions & 0 deletions packages/dashboard-backend/src/constants/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,50 @@ export const aiProviderKeyParamsSchema: JSONSchema7 = {
required: ['namespace', 'toolId'],
};

export const deviceAuthTokenParamsSchema: JSONSchema7 = {
type: 'object',
properties: {
namespace: {
type: 'string',
Comment thread
olexii4 marked this conversation as resolved.
},
tokenName: {
type: 'string',
pattern: '^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$',
maxLength: 253,
},
},
required: ['namespace', 'tokenName'],
};

export const deviceAuthPollBodySchema: JSONSchema7 = {
type: 'object',
required: ['deviceCode'],
properties: {
deviceCode: { type: 'string', minLength: 1, maxLength: 100, pattern: '^[a-zA-Z0-9_-]+$' },
},
};

export const deviceAuthTokenResponseSchema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
provider: { type: 'string' },
creationTimestamp: { type: 'string' },
},
required: ['name'],
},
};

export const deviceAuthValidateResponseSchema = {
type: 'object',
properties: {
valid: { type: 'string', enum: ['valid', 'invalid', 'unknown'] },
},
required: ['valid'],
};

// namespaced schemas

export const namespacedSchema: JSONSchema7 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IAiProviderKeyApi,
IAiRegistryApi,
IAirGapSampleApi,
IDeviceAuthTokenApi,
IDevWorkspaceApi,
IDevWorkspaceClusterApi,
IDevWorkspaceSingletonClient,
Expand Down Expand Up @@ -93,6 +94,10 @@ export class DevWorkspaceClient implements IDevWorkspaceClient {
throw new Error('Method not implemented.');
}

get deviceAuthTokenApi(): IDeviceAuthTokenApi {
throw new Error('Method not implemented.');
}

get sccPermissionApi(): ISccPermissionApi {
throw new Error('Method not implemented.');
}
Expand Down
8 changes: 8 additions & 0 deletions packages/dashboard-backend/src/devworkspaceClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as k8s from '@kubernetes/client-node';
import { AiProviderKeyApiService } from '@/devworkspaceClient/services/aiProviderKeyApi';
import { AiRegistryApiService } from '@/devworkspaceClient/services/aiRegistryApi';
import { AirGapSampleApiService } from '@/devworkspaceClient/services/airGapSampleApi';
import { GitHubDeviceAuthTokenApiService } from '@/devworkspaceClient/services/deviceAuthTokenApi';
import { DevWorkspaceApiService } from '@/devworkspaceClient/services/devWorkspaceApi';
import { DevWorkspaceClusterApiService } from '@/devworkspaceClient/services/devWorkspaceClusterApiService';
import { DevWorkspaceTemplateApiService } from '@/devworkspaceClient/services/devWorkspaceTemplateApi';
Expand All @@ -36,6 +37,7 @@ import {
IAiProviderKeyApi,
IAiRegistryApi,
IAirGapSampleApi,
IDeviceAuthTokenApi,
IDevWorkspaceApi,
IDevWorkspaceClient,
IDevWorkspaceClusterApi,
Expand Down Expand Up @@ -141,6 +143,12 @@ export class DevWorkspaceClient implements IDevWorkspaceClient {
return new AiRegistryApiService(this.kubeConfig);
}

private _deviceAuthTokenApi: IDeviceAuthTokenApi | undefined;

get deviceAuthTokenApi(): IDeviceAuthTokenApi {
Comment thread
olexii4 marked this conversation as resolved.
return (this._deviceAuthTokenApi ??= new GitHubDeviceAuthTokenApiService(this.kubeConfig));
}

get sccPermissionApi(): ISccPermissionApi {
return new SccPermissionApiService(this.kubeConfig);
}
Expand Down
Loading
Loading