This is an automated email from the ASF dual-hosted git repository.

jasonliu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 95c1e3c6f2d feat: added /ui/auth/me to private endpoints (#58292)
95c1e3c6f2d is described below

commit 95c1e3c6f2d8c1a38233cca2761df5e7034e90e2
Author: Aaron Wolmutt <[email protected]>
AuthorDate: Wed Nov 26 20:11:46 2025 -0600

    feat: added /ui/auth/me to private endpoints (#58292)
    
    * feat: added /ui/auth/me to private endpoints
    
    * add username and role to BaseUser class
    
    * fixed base user and response type for ci
    
    * fix: change base user interface for consistency and fallbacks between 
auth providers
    
    * feat: added /ui/auth/me endpoint for fab auth
    
    * fix mypy errors without changing existing providers
    
    * remove change from Keycloack user
    
    * fix KeyCloak tests
    
    * remove provider import, add method to check auth manager type
    
    * remove extra pydantic fields
    
    * modified required pydantic fields for BaseUser based on child auth 
manager users. ran prek --all-files
    
    * add comment. set username to optional and add get_groups method for aws 
auth manager pluggability
    
    * worked on BaseUser api contracts. added empty string fallback to 
/ui/auth/me endpoint
    
    * fixed failing unit test
    
    * decoupled /ui/auth/me endpoint from existing providers and BaseUser
    
    * remove pydantic attriubtes
    
    * removed change made by vscode github co pilot agent
    
    * remove docstring added by agent
    
    * fix ci
    
    * changed field name to ui_attributes
    
    * removed ui_attributes field
    
    * deleted init file
---
 .../api_fastapi/core_api/datamodels/ui/auth.py     |  7 +++++
 .../api_fastapi/core_api/openapi/_private_ui.yaml  | 31 ++++++++++++++++++++++
 .../airflow/api_fastapi/core_api/routes/ui/auth.py | 12 +++++++++
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  4 +++
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  7 +++++
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  7 +++++
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  7 +++++
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  7 +++++
 .../airflow/ui/openapi-gen/requests/schemas.gen.ts | 17 ++++++++++++
 .../ui/openapi-gen/requests/services.gen.ts        | 15 ++++++++++-
 .../airflow/ui/openapi-gen/requests/types.gen.ts   | 20 ++++++++++++++
 .../api_fastapi/core_api/routes/ui/test_auth.py    | 18 +++++++++++++
 12 files changed, 151 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
index 736f4ffdc06..8cc6c4648a2 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/auth.py
@@ -26,3 +26,10 @@ class MenuItemCollectionResponse(BaseModel):
 
     authorized_menu_items: list[MenuItem]
     extra_menu_items: list[ExtraMenuItem]
+
+
+class AuthenticatedMeResponse(BaseModel):
+    """Authenticated user information serializer for responses."""
+
+    id: str
+    username: str
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index 4061b761003..b4e93a4c9f2 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -23,6 +23,23 @@ paths:
       security:
       - OAuth2PasswordBearer: []
       - HTTPBearer: []
+  /ui/auth/me:
+    get:
+      tags:
+      - Auth Links
+      summary: Get Current User Info
+      description: Convienently get the current authenticated user information.
+      operationId: get_current_user_info
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/AuthenticatedMeResponse'
+      security:
+      - OAuth2PasswordBearer: []
+      - HTTPBearer: []
   /ui/next_run_assets/{dag_id}:
     get:
       tags:
@@ -1079,6 +1096,20 @@ paths:
                 $ref: '#/components/schemas/HTTPValidationError'
 components:
   schemas:
+    AuthenticatedMeResponse:
+      properties:
+        id:
+          type: string
+          title: Id
+        username:
+          type: string
+          title: Username
+      type: object
+      required:
+      - id
+      - username
+      title: AuthenticatedMeResponse
+      description: Authenticated user information serializer for responses.
     BackfillCollectionResponse:
       properties:
         backfills:
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
index f3c20a9ccef..7927f810d49 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/auth.py
@@ -20,6 +20,7 @@ from __future__ import annotations
 from airflow.api_fastapi.app import get_auth_manager
 from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.datamodels.ui.auth import (
+    AuthenticatedMeResponse,
     MenuItemCollectionResponse,
 )
 from airflow.api_fastapi.core_api.security import GetUserDep
@@ -38,3 +39,14 @@ def get_auth_menus(
         authorized_menu_items=authorized_menu_items,
         extra_menu_items=extra_menu_items,
     )
+
+
+@auth_router.get("/auth/me")
+def get_current_user_info(
+    user: GetUserDep,
+) -> AuthenticatedMeResponse:
+    """Convienently get the current authenticated user information."""
+    return AuthenticatedMeResponse(
+        id=user.get_id(),
+        username=user.get_name(),
+    )
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 9453643554c..3a3ea3dd54e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -772,6 +772,10 @@ export type AuthLinksServiceGetAuthMenusDefaultResponse = 
Awaited<ReturnType<typ
 export type AuthLinksServiceGetAuthMenusQueryResult<TData = 
AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
 export const useAuthLinksServiceGetAuthMenusKey = 
"AuthLinksServiceGetAuthMenus";
 export const UseAuthLinksServiceGetAuthMenusKeyFn = (queryKey?: 
Array<unknown>) => [useAuthLinksServiceGetAuthMenusKey, ...(queryKey ?? [])];
+export type AuthLinksServiceGetCurrentUserInfoDefaultResponse = 
Awaited<ReturnType<typeof AuthLinksService.getCurrentUserInfo>>;
+export type AuthLinksServiceGetCurrentUserInfoQueryResult<TData = 
AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
+export const useAuthLinksServiceGetCurrentUserInfoKey = 
"AuthLinksServiceGetCurrentUserInfo";
+export const UseAuthLinksServiceGetCurrentUserInfoKeyFn = (queryKey?: 
Array<unknown>) => [useAuthLinksServiceGetCurrentUserInfoKey, ...(queryKey ?? 
[])];
 export type DependenciesServiceGetDependenciesDefaultResponse = 
Awaited<ReturnType<typeof DependenciesService.getDependencies>>;
 export type DependenciesServiceGetDependenciesQueryResult<TData = 
DependenciesServiceGetDependenciesDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
 export const useDependenciesServiceGetDependenciesKey = 
"DependenciesServiceGetDependencies";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 939dbfa7987..6ff5dc02fb5 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1467,6 +1467,13 @@ export const ensureUseLoginServiceLogoutData = 
(queryClient: QueryClient) => que
 */
 export const ensureUseAuthLinksServiceGetAuthMenusData = (queryClient: 
QueryClient) => queryClient.ensureQueryData({ queryKey: 
Common.UseAuthLinksServiceGetAuthMenusKeyFn(), queryFn: () => 
AuthLinksService.getAuthMenus() });
 /**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const ensureUseAuthLinksServiceGetCurrentUserInfoData = (queryClient: 
QueryClient) => queryClient.ensureQueryData({ queryKey: 
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(), queryFn: () => 
AuthLinksService.getCurrentUserInfo() });
+/**
 * Get Dependencies
 * Dependencies graph.
 * @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 12d4fd68ea1..b8eaa08e038 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1467,6 +1467,13 @@ export const prefetchUseLoginServiceLogout = 
(queryClient: QueryClient) => query
 */
 export const prefetchUseAuthLinksServiceGetAuthMenus = (queryClient: 
QueryClient) => queryClient.prefetchQuery({ queryKey: 
Common.UseAuthLinksServiceGetAuthMenusKeyFn(), queryFn: () => 
AuthLinksService.getAuthMenus() });
 /**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const prefetchUseAuthLinksServiceGetCurrentUserInfo = (queryClient: 
QueryClient) => queryClient.prefetchQuery({ queryKey: 
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(), queryFn: () => 
AuthLinksService.getCurrentUserInfo() });
+/**
 * Get Dependencies
 * Dependencies graph.
 * @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 3d20b53573f..57cb60e7332 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1467,6 +1467,13 @@ export const useLoginServiceLogout = <TData = 
Common.LoginServiceLogoutDefaultRe
 */
 export const useAuthLinksServiceGetAuthMenus = <TData = 
Common.AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: 
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => 
useQuery<TData, TError>({ queryKey: 
Common.UseAuthLinksServiceGetAuthMenusKeyFn(queryKey), queryFn: () => 
AuthLinksService.getAuthMenus() as TData, ...options });
 /**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const useAuthLinksServiceGetCurrentUserInfo = <TData = 
Common.AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: 
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => 
useQuery<TData, TError>({ queryKey: 
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(queryKey), queryFn: () => 
AuthLinksService.getCurrentUserInfo() as TData, ...options });
+/**
 * Get Dependencies
 * Dependencies graph.
 * @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 2745bee178c..47cd4bc8289 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1467,6 +1467,13 @@ export const useLoginServiceLogoutSuspense = <TData = 
Common.LoginServiceLogoutD
 */
 export const useAuthLinksServiceGetAuthMenusSuspense = <TData = 
Common.AuthLinksServiceGetAuthMenusDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: 
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => 
useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAuthLinksServiceGetAuthMenusKeyFn(queryKey), queryFn: () => 
AuthLinksService.getAuthMenus() as TData, ...options });
 /**
+* Get Current User Info
+* Convienently get the current authenticated user information.
+* @returns AuthenticatedMeResponse Successful Response
+* @throws ApiError
+*/
+export const useAuthLinksServiceGetCurrentUserInfoSuspense = <TData = 
Common.AuthLinksServiceGetCurrentUserInfoDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: 
Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => 
useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAuthLinksServiceGetCurrentUserInfoKeyFn(queryKey), queryFn: () => 
AuthLinksService.getCurrentUserInfo() as TData, ...options });
+/**
 * Get Dependencies
 * Dependencies graph.
 * @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 5142b744a77..a9b70b7dbbb 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -6904,6 +6904,23 @@ export const $XComUpdateBody = {
     description: 'Payload serializer for updating an XCom entry.'
 } as const;
 
+export const $AuthenticatedMeResponse = {
+    properties: {
+        id: {
+            type: 'string',
+            title: 'Id'
+        },
+        username: {
+            type: 'string',
+            title: 'Username'
+        }
+    },
+    type: 'object',
+    required: ['id', 'username'],
+    title: 'AuthenticatedMeResponse',
+    description: 'Authenticated user information serializer for responses.'
+} as const;
+
 export const $BaseEdgeResponse = {
     properties: {
         source_id: {
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index dea17f7b8f6..f01a1f9be69 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -3,7 +3,7 @@
 import type { CancelablePromise } from './core/CancelablePromise';
 import { OpenAPI } from './core/OpenAPI';
 import { request as __request } from './core/request';
-import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, 
GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, 
GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, 
CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, 
GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, 
DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, 
GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, 
Dele [...]
+import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, 
GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, 
GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, 
CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, 
GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, 
DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, 
GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, 
Dele [...]
 
 export class AssetService {
     /**
@@ -3760,6 +3760,19 @@ export class AuthLinksService {
         });
     }
     
+    /**
+     * Get Current User Info
+     * Convienently get the current authenticated user information.
+     * @returns AuthenticatedMeResponse Successful Response
+     * @throws ApiError
+     */
+    public static getCurrentUserInfo(): 
CancelablePromise<GetCurrentUserInfoResponse> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/ui/auth/me'
+        });
+    }
+    
 }
 
 export class DependenciesService {
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 9704369f913..b65cd4d2d45 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1696,6 +1696,14 @@ export type XComUpdateBody = {
     map_index?: number;
 };
 
+/**
+ * Authenticated user information serializer for responses.
+ */
+export type AuthenticatedMeResponse = {
+    id: string;
+    username: string;
+};
+
 /**
  * Base Edge serializer for responses.
  */
@@ -3334,6 +3342,8 @@ export type LogoutResponse = unknown;
 
 export type GetAuthMenusResponse = MenuItemCollectionResponse;
 
+export type GetCurrentUserInfoResponse = AuthenticatedMeResponse;
+
 export type GetDependenciesData = {
     nodeId?: string | null;
 };
@@ -6429,6 +6439,16 @@ export type $OpenApiTs = {
             };
         };
     };
+    '/ui/auth/me': {
+        get: {
+            res: {
+                /**
+                 * Successful Response
+                 */
+                200: AuthenticatedMeResponse;
+            };
+        };
+    };
     '/ui/dependencies': {
         get: {
             req: GetDependenciesData;
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
index 1b37bc4ba86..98a46ea08a6 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_auth.py
@@ -58,3 +58,21 @@ class TestGetAuthLinks:
         response = unauthorized_test_client.get("/auth/menus")
         assert response.status_code == 200
         assert response.json() == {"authorized_menu_items": [], 
"extra_menu_items": []}
+
+
+class TestGetMeResponse:
+    def test_should_response_200_with_authenticated_user(self, test_client):
+        """Test /auth/me endpoint with SimpleAuthManager authenticated user."""
+        response = test_client.get("/auth/me")
+
+        assert response.status_code == 200
+        assert response.json() == {
+            "username": "test",
+            "id": "test",
+        }
+
+    def test_with_unauthenticated_user(self, unauthenticated_test_client):
+        """Test /auth/me endpoint with no authentication."""
+        response = unauthenticated_test_client.get("/auth/me")
+        assert response.status_code == 401
+        assert response.json() == {"detail": "Not authenticated"}

Reply via email to