diff --git a/api-contracts/openapi/components/schemas/metadata.yaml b/api-contracts/openapi/components/schemas/metadata.yaml index b932aca9df..d6f5d257ac 100644 --- a/api-contracts/openapi/components/schemas/metadata.yaml +++ b/api-contracts/openapi/components/schemas/metadata.yaml @@ -53,6 +53,7 @@ APIMetaAuth: example: - basic - google + - azure APIMetaPosthog: type: object diff --git a/api-contracts/openapi/openapi.yaml b/api-contracts/openapi/openapi.yaml index 8cfe9bbdd6..3ce45b5774 100644 --- a/api-contracts/openapi/openapi.yaml +++ b/api-contracts/openapi/openapi.yaml @@ -103,6 +103,10 @@ paths: $ref: "./paths/user/user.yaml#/oauth-start-github" /api/v1/users/github/callback: $ref: "./paths/user/user.yaml#/oauth-callback-github" + /api/v1/users/azure/start: + $ref: "./paths/user/user.yaml#/oauth-start-azure" + /api/v1/users/azure/callback: + $ref: "./paths/user/user.yaml#/oauth-callback-azure" /api/v1/tenants/{tenant}/slack/start: $ref: "./paths/user/user.yaml#/oauth-start-slack" /api/v1/users/slack/callback: diff --git a/api-contracts/openapi/paths/user/user.yaml b/api-contracts/openapi/paths/user/user.yaml index 8832267ab3..db35b28738 100644 --- a/api-contracts/openapi/paths/user/user.yaml +++ b/api-contracts/openapi/paths/user/user.yaml @@ -228,6 +228,36 @@ oauth-callback-github: summary: Complete OAuth flow tags: - User +oauth-start-azure: + get: + description: Starts the OAuth flow + operationId: user:update:azure-oauth-start + responses: + "302": + description: Successfully started the OAuth flow + headers: + location: + schema: + type: string + security: [] + summary: Start OAuth flow + tags: + - User +oauth-callback-azure: + get: + description: Completes the OAuth flow + operationId: user:update:azure-oauth-callback + responses: + "302": + description: Successfully completed the OAuth flow + headers: + location: + schema: + type: string + security: [] + summary: Complete OAuth flow + tags: + - User oauth-start-slack: get: x-resources: ["tenant"] diff --git a/api/v1/server/authz/rbac.yaml b/api/v1/server/authz/rbac.yaml index 481af2e7fb..63478ddd12 100644 --- a/api/v1/server/authz/rbac.yaml +++ b/api/v1/server/authz/rbac.yaml @@ -82,6 +82,7 @@ roles: - WebhookCreate - CloudMetadataGet - UserUpdateGoogleOauthCallback + - UserUpdateAzureOauthCallback - UserUpdateSlackOauthCallback - V1WorkflowRunCreate - V1WorkflowRunDisplayNamesList @@ -111,6 +112,7 @@ roles: - TenantResourcePolicyGet - UserUpdateSlackOauthStart - UserUpdateGoogleOauthStart + - UserUpdateAzureOauthStart - SlackWebhookDelete - V1CelDebug - V1WorkflowRunGetTimings diff --git a/api/v1/server/handlers/metadata/get.go b/api/v1/server/handlers/metadata/get.go index 97c770763e..8383bbbf62 100644 --- a/api/v1/server/handlers/metadata/get.go +++ b/api/v1/server/handlers/metadata/get.go @@ -22,6 +22,10 @@ func (u *MetadataService) MetadataGet(ctx echo.Context, request gen.MetadataGetR authTypes = append(authTypes, "github") } + if u.config.Auth.ConfigFile.Azure.Enabled { + authTypes = append(authTypes, "azure") + } + pylonAppID := u.config.Pylon.AppID var posthogConfig *gen.APIMetaPosthog diff --git a/api/v1/server/handlers/users/azure_oauth_callback.go b/api/v1/server/handlers/users/azure_oauth_callback.go new file mode 100644 index 0000000000..1afd360879 --- /dev/null +++ b/api/v1/server/handlers/users/azure_oauth_callback.go @@ -0,0 +1,202 @@ +package users + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/jackc/pgx/v5" + "github.com/labstack/echo/v4" + "golang.org/x/oauth2" + + "github.com/hatchet-dev/hatchet/api/v1/server/authn" + "github.com/hatchet-dev/hatchet/api/v1/server/middleware/redirect" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/pkg/analytics" + "github.com/hatchet-dev/hatchet/pkg/config/server" + v1 "github.com/hatchet-dev/hatchet/pkg/repository" + "github.com/hatchet-dev/hatchet/pkg/repository/sqlcv1" +) + +// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow. +func (u *UserService) UserUpdateAzureOauthCallback(ctx echo.Context, _ gen.UserUpdateAzureOauthCallbackRequestObject) (gen.UserUpdateAzureOauthCallbackResponseObject, error) { + isValid, _, err := authn.NewSessionHelpers(u.config.SessionStore).ValidateOAuthState(ctx, "azure") + + if err != nil || !isValid { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Could not log in. Please try again and make sure cookies are enabled.") + } + + token, err := u.config.Auth.AzureOAuthConfig.Exchange(context.Background(), ctx.Request().URL.Query().Get("code")) + + if err != nil { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Forbidden") + } + + if !token.Valid() { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, fmt.Errorf("invalid token"), "Forbidden") + } + + user, err := u.upsertAzureUserFromToken(ctx.Request().Context(), u.config, token) + + if err != nil { + if errors.Is(err, ErrNotInRestrictedDomain) { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Email is not in the restricted domain group.") + } + + if errors.Is(err, ErrAzureNoEmail) { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Azure account must have an email.") + } + + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Internal error.") + } + + err = authn.NewSessionHelpers(u.config.SessionStore).SaveAuthenticated(ctx, user) + + if err != nil { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Internal error.") + } + + analyticsCtx := context.WithValue(ctx.Request().Context(), analytics.UserIDKey, user.ID) + analyticsCtx = context.WithValue(analyticsCtx, analytics.SourceKey, analytics.SourceUI) + u.config.Analytics.Enqueue( + analyticsCtx, + analytics.User, analytics.Login, + user.ID.String(), + map[string]interface{}{"provider": "azure"}, + ) + return gen.UserUpdateAzureOauthCallback302Response{ + Headers: gen.UserUpdateAzureOauthCallback302ResponseHeaders{ + Location: u.config.Runtime.ServerURL, + }, + }, nil +} + +func (u *UserService) upsertAzureUserFromToken(ctx context.Context, config *server.ServerConfig, tok *oauth2.Token) (*sqlcv1.User, error) { + aInfo, err := getAzureUserInfoFromToken(tok) + if err != nil { + return nil, err + } + + // Azure AD does not expose a Google-style "hd" (hosted domain) claim, so we + // gate on the email's domain like the GitHub provider does. + if err := u.checkUserRestrictionsForEmail(config, aInfo.Email); err != nil { + return nil, err + } + + expiresAt := tok.Expiry + + // use the encryption service to encrypt the access and refresh token + accessTokenEncrypted, err := config.Encryption.Encrypt([]byte(tok.AccessToken), "azure_access_token") + + if err != nil { + return nil, fmt.Errorf("failed to encrypt access token: %s", err.Error()) + } + + refreshTokenEncrypted, err := config.Encryption.Encrypt([]byte(tok.RefreshToken), "azure_refresh_token") + + if err != nil { + return nil, fmt.Errorf("failed to encrypt refresh token: %s", err.Error()) + } + + oauthOpts := &v1.OAuthOpts{ + Provider: "azure", + ProviderUserId: aInfo.Sub, + AccessToken: accessTokenEncrypted, + RefreshToken: refreshTokenEncrypted, + ExpiresAt: &expiresAt, + } + + // Azure AD's OIDC userinfo endpoint does not return an email_verified claim, and + // Hatchet's authz middleware blocks users whose email is unverified. The email is + // sourced from the Azure AD directory (not self-asserted), so we treat it as verified, + // consistent with the Google/GitHub providers whose OAuth users are always verified. + user, err := u.config.V1.User().GetUserByEmail(ctx, aInfo.Email) + + switch err { + case nil: + user, err = u.config.V1.User().UpdateUser(ctx, user.ID, &v1.UpdateUserOpts{ + EmailVerified: v1.BoolPtr(true), + Name: v1.StringPtr(aInfo.Name), + OAuth: oauthOpts, + }) + + if err != nil { + return nil, fmt.Errorf("failed to update user: %s", err.Error()) + } + case pgx.ErrNoRows: + user, err = u.config.V1.User().CreateUser(ctx, &v1.CreateUserOpts{ + Email: aInfo.Email, + EmailVerified: v1.BoolPtr(true), + Name: v1.StringPtr(aInfo.Name), + OAuth: oauthOpts, + }) + + if err != nil { + return nil, fmt.Errorf("failed to create user: %s", err.Error()) + } + default: + return nil, fmt.Errorf("failed to get user: %s", err.Error()) + } + + return user, nil +} + +var ErrAzureNoEmail = fmt.Errorf("azure account must have an email") + +// azureUserInfo holds the claims we consume from the Microsoft identity platform +// OIDC userinfo endpoint. Per the Microsoft docs that endpoint returns only sub, +// name, family_name, given_name, picture and (with the "email" scope) email — it +// does not return email_verified or preferred_username, so we don't model those. +type azureUserInfo struct { + Sub string `json:"sub"` + Name string `json:"name"` + Email string `json:"email"` +} + +func getAzureUserInfoFromToken(tok *oauth2.Token) (*azureUserInfo, error) { + // use the Microsoft identity platform OIDC userinfo endpoint to get claims + url := "https://graph.microsoft.com/oidc/userinfo" + + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return nil, fmt.Errorf("failed creating request: %s", err.Error()) + } + + req.Header.Add("Authorization", "Bearer "+tok.AccessToken) + + client := &http.Client{} + + response, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed getting user info: %s", err.Error()) + } + + defer response.Body.Close() + + contents, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("failed reading response body: %s", err.Error()) + } + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("userinfo endpoint returned status %d: %s", response.StatusCode, string(contents)) + } + + // parse contents into Azure userinfo claims + aInfo := &azureUserInfo{} + err = json.Unmarshal(contents, &aInfo) + + if err != nil { + return nil, fmt.Errorf("failed parsing response body: %s", err.Error()) + } + + if aInfo.Email == "" { + return nil, ErrAzureNoEmail + } + + return aInfo, nil +} diff --git a/api/v1/server/handlers/users/azure_oauth_start.go b/api/v1/server/handlers/users/azure_oauth_start.go new file mode 100644 index 0000000000..106d3f13dd --- /dev/null +++ b/api/v1/server/handlers/users/azure_oauth_start.go @@ -0,0 +1,30 @@ +package users + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/authn" + "github.com/hatchet-dev/hatchet/api/v1/server/middleware/redirect" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" +) + +// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow. +func (u *UserService) UserUpdateAzureOauthStart(ctx echo.Context, _ gen.UserUpdateAzureOauthStartRequestObject) (gen.UserUpdateAzureOauthStartResponseObject, error) { + if !u.config.Runtime.AllowSignup { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, nil, "User signup is disabled.") + } + + state, err := authn.NewSessionHelpers(u.config.SessionStore).SaveOAuthState(ctx, "azure") + + if err != nil { + return nil, redirect.GetRedirectWithError(ctx, u.config.Logger, err, "Could not get cookie. Please make sure cookies are enabled.") + } + + url := u.config.Auth.AzureOAuthConfig.AuthCodeURL(state) + + return gen.UserUpdateAzureOauthStart302Response{ + Headers: gen.UserUpdateAzureOauthStart302ResponseHeaders{ + Location: url, + }, + }, nil +} diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index df602e533c..0655645df1 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -3818,6 +3818,12 @@ type ServerInterface interface { // Get workflow worker count // (GET /api/v1/tenants/{tenant}/workflows/{workflow}/worker-count) WorkflowGetWorkersCount(ctx echo.Context, tenant openapi_types.UUID, workflow openapi_types.UUID) error + // Complete OAuth flow + // (GET /api/v1/users/azure/callback) + UserUpdateAzureOauthCallback(ctx echo.Context) error + // Start OAuth flow + // (GET /api/v1/users/azure/start) + UserUpdateAzureOauthStart(ctx echo.Context) error // Get current user // (GET /api/v1/users/current) UserGetCurrent(ctx echo.Context) error @@ -7608,6 +7614,24 @@ func (w *ServerInterfaceWrapper) WorkflowGetWorkersCount(ctx echo.Context) error return err } +// UserUpdateAzureOauthCallback converts echo context to params. +func (w *ServerInterfaceWrapper) UserUpdateAzureOauthCallback(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UserUpdateAzureOauthCallback(ctx) + return err +} + +// UserUpdateAzureOauthStart converts echo context to params. +func (w *ServerInterfaceWrapper) UserUpdateAzureOauthStart(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UserUpdateAzureOauthStart(ctx) + return err +} + // UserGetCurrent converts echo context to params. func (w *ServerInterfaceWrapper) UserGetCurrent(ctx echo.Context) error { var err error @@ -8143,6 +8167,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/api/v1/tenants/:tenant/workflows/:workflow/crons", wrapper.CronWorkflowTriggerCreate) router.POST(baseURL+"/api/v1/tenants/:tenant/workflows/:workflow/scheduled", wrapper.ScheduledWorkflowRunCreate) router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/:workflow/worker-count", wrapper.WorkflowGetWorkersCount) + router.GET(baseURL+"/api/v1/users/azure/callback", wrapper.UserUpdateAzureOauthCallback) + router.GET(baseURL+"/api/v1/users/azure/start", wrapper.UserUpdateAzureOauthStart) router.GET(baseURL+"/api/v1/users/current", wrapper.UserGetCurrent) router.GET(baseURL+"/api/v1/users/github/callback", wrapper.UserUpdateGithubOauthCallback) router.GET(baseURL+"/api/v1/users/github/start", wrapper.UserUpdateGithubOauthStart) @@ -12921,6 +12947,48 @@ func (response WorkflowGetWorkersCount403JSONResponse) VisitWorkflowGetWorkersCo return json.NewEncoder(w).Encode(response) } +type UserUpdateAzureOauthCallbackRequestObject struct { +} + +type UserUpdateAzureOauthCallbackResponseObject interface { + VisitUserUpdateAzureOauthCallbackResponse(w http.ResponseWriter) error +} + +type UserUpdateAzureOauthCallback302ResponseHeaders struct { + Location string +} + +type UserUpdateAzureOauthCallback302Response struct { + Headers UserUpdateAzureOauthCallback302ResponseHeaders +} + +func (response UserUpdateAzureOauthCallback302Response) VisitUserUpdateAzureOauthCallbackResponse(w http.ResponseWriter) error { + w.Header().Set("location", fmt.Sprint(response.Headers.Location)) + w.WriteHeader(302) + return nil +} + +type UserUpdateAzureOauthStartRequestObject struct { +} + +type UserUpdateAzureOauthStartResponseObject interface { + VisitUserUpdateAzureOauthStartResponse(w http.ResponseWriter) error +} + +type UserUpdateAzureOauthStart302ResponseHeaders struct { + Location string +} + +type UserUpdateAzureOauthStart302Response struct { + Headers UserUpdateAzureOauthStart302ResponseHeaders +} + +func (response UserUpdateAzureOauthStart302Response) VisitUserUpdateAzureOauthStartResponse(w http.ResponseWriter) error { + w.Header().Set("location", fmt.Sprint(response.Headers.Location)) + w.WriteHeader(302) + return nil +} + type UserGetCurrentRequestObject struct { } @@ -14088,6 +14156,10 @@ type StrictServerInterface interface { WorkflowGetWorkersCount(ctx echo.Context, request WorkflowGetWorkersCountRequestObject) (WorkflowGetWorkersCountResponseObject, error) + UserUpdateAzureOauthCallback(ctx echo.Context, request UserUpdateAzureOauthCallbackRequestObject) (UserUpdateAzureOauthCallbackResponseObject, error) + + UserUpdateAzureOauthStart(ctx echo.Context, request UserUpdateAzureOauthStartRequestObject) (UserUpdateAzureOauthStartResponseObject, error) + UserGetCurrent(ctx echo.Context, request UserGetCurrentRequestObject) (UserGetCurrentResponseObject, error) UserUpdateGithubOauthCallback(ctx echo.Context, request UserUpdateGithubOauthCallbackRequestObject) (UserUpdateGithubOauthCallbackResponseObject, error) @@ -16990,6 +17062,46 @@ func (sh *strictHandler) WorkflowGetWorkersCount(ctx echo.Context, tenant openap return nil } +// UserUpdateAzureOauthCallback operation +func (sh *strictHandler) UserUpdateAzureOauthCallback(ctx echo.Context) error { + var request UserUpdateAzureOauthCallbackRequestObject + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.UserUpdateAzureOauthCallback(ctx, request.(UserUpdateAzureOauthCallbackRequestObject)) + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(UserUpdateAzureOauthCallbackResponseObject); ok { + return validResponse.VisitUserUpdateAzureOauthCallbackResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// UserUpdateAzureOauthStart operation +func (sh *strictHandler) UserUpdateAzureOauthStart(ctx echo.Context) error { + var request UserUpdateAzureOauthStartRequestObject + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.UserUpdateAzureOauthStart(ctx, request.(UserUpdateAzureOauthStartRequestObject)) + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(UserUpdateAzureOauthStartResponseObject); ok { + return validResponse.VisitUserUpdateAzureOauthStartResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // UserGetCurrent operation func (sh *strictHandler) UserGetCurrent(ctx echo.Context) error { var request UserGetCurrentRequestObject @@ -17564,372 +17676,372 @@ func (sh *strictHandler) WorkflowVersionGet(ctx echo.Context, workflow openapi_t // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "8/3L9/8XAAD//wmjcL0zigMA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/frontend/app/src/components/v1/ui/icons.tsx b/frontend/app/src/components/v1/ui/icons.tsx index 6be550f956..4f949802ad 100644 --- a/frontend/app/src/components/v1/ui/icons.tsx +++ b/frontend/app/src/components/v1/ui/icons.tsx @@ -113,6 +113,14 @@ export const Icons = { /> ), + azure: (props: IconProps) => ( + + + + + + + ), apple: (props: IconProps) => ( ([]) }); + /** + * @description Starts the OAuth flow + * + * @tags User + * @name UserUpdateAzureOauthStart + * @summary Start OAuth flow + * @request GET:/api/v1/users/azure/start + */ + userUpdateAzureOauthStart = Object.assign((params: RequestParams = {}) => + this.request({ + path: `/api/v1/users/azure/start`, + method: "GET", + ...params, + xResources: [], + }), { resources: new Set([]) }); + /** + * @description Completes the OAuth flow + * + * @tags User + * @name UserUpdateAzureOauthCallback + * @summary Complete OAuth flow + * @request GET:/api/v1/users/azure/callback + */ + userUpdateAzureOauthCallback = Object.assign((params: RequestParams = {}) => + this.request({ + path: `/api/v1/users/azure/callback`, + method: "GET", + ...params, + xResources: [], + }), { resources: new Set([]) }); /** * @description Starts the OAuth flow * diff --git a/frontend/app/src/lib/api/generated/control-plane/Api.ts b/frontend/app/src/lib/api/generated/control-plane/Api.ts index e99ce35d80..9e8adbc578 100644 --- a/frontend/app/src/lib/api/generated/control-plane/Api.ts +++ b/frontend/app/src/lib/api/generated/control-plane/Api.ts @@ -254,6 +254,34 @@ export class Api< method: "GET", ...params, }); + /** + * @description Starts the OAuth flow + * + * @tags User + * @name CloudUserUpdateAzureOauthStart + * @summary Start OAuth flow + * @request GET:/api/v1/control-plane/users/azure/start + */ + cloudUserUpdateAzureOauthStart = (params: RequestParams = {}) => + this.request({ + path: `/api/v1/control-plane/users/azure/start`, + method: "GET", + ...params, + }); + /** + * @description Completes the OAuth flow + * + * @tags User + * @name CloudUserUpdateAzureOauthCallback + * @summary Complete OAuth flow + * @request GET:/api/v1/control-plane/users/azure/callback + */ + cloudUserUpdateAzureOauthCallback = (params: RequestParams = {}) => + this.request({ + path: `/api/v1/control-plane/users/azure/callback`, + method: "GET", + ...params, + }); /** * @description Starts the OAuth flow * diff --git a/frontend/app/src/lib/api/generated/data-contracts.ts b/frontend/app/src/lib/api/generated/data-contracts.ts index 357caaff19..cdfc7a0144 100644 --- a/frontend/app/src/lib/api/generated/data-contracts.ts +++ b/frontend/app/src/lib/api/generated/data-contracts.ts @@ -1254,7 +1254,7 @@ export interface V1CELDebugResponse { export interface APIMetaAuth { /** * the supported types of authentication - * @example ["basic","google"] + * @example ["basic","google","azure"] */ schemes?: string[]; } diff --git a/frontend/app/src/lib/api/user-wrapper.ts b/frontend/app/src/lib/api/user-wrapper.ts index 290e21be03..31feeb7116 100644 --- a/frontend/app/src/lib/api/user-wrapper.ts +++ b/frontend/app/src/lib/api/user-wrapper.ts @@ -63,6 +63,26 @@ export function useUserApi() { ).data, }), + userUpdateAzureOauthStartQuery: () => ({ + queryKey: ['user:azure-oauth:start'] as const, + queryFn: async () => + ( + await (isControlPlaneEnabled + ? controlPlaneApi.cloudUserUpdateAzureOauthStart() + : api.userUpdateAzureOauthStart()) + ).data, + }), + + userUpdateAzureOauthCallbackQuery: () => ({ + queryKey: ['user:azure-oauth:callback'] as const, + queryFn: async () => + ( + await (isControlPlaneEnabled + ? controlPlaneApi.cloudUserUpdateAzureOauthCallback() + : api.userUpdateAzureOauthCallback()) + ).data, + }), + // ── Mutations ────────────────────────────────────────────────────────── userUpdateLoginMutation: () => ({ diff --git a/frontend/app/src/pages/auth/components/auth-page.tsx b/frontend/app/src/pages/auth/components/auth-page.tsx index 39be31bf16..85629682e2 100644 --- a/frontend/app/src/pages/auth/components/auth-page.tsx +++ b/frontend/app/src/pages/auth/components/auth-page.tsx @@ -28,13 +28,15 @@ export function AuthPage({ const basicEnabled = schemes.includes('basic'); const googleEnabled = schemes.includes('google'); const githubEnabled = schemes.includes('github'); + const azureEnabled = schemes.includes('azure'); const ssoEnabled = schemes.includes('sso'); const providers = [ googleEnabled && 'google', githubEnabled && 'github', + azureEnabled && 'azure', ssoEnabled && 'sso', - ].filter(Boolean) as Array<'google' | 'github' | 'sso'>; + ].filter(Boolean) as Array<'google' | 'github' | 'azure' | 'sso'>; const sections = [ providers.length > 0 && ( diff --git a/frontend/app/src/pages/auth/components/social-auth.tsx b/frontend/app/src/pages/auth/components/social-auth.tsx index 4cd4f1f6e3..99b32e26cd 100644 --- a/frontend/app/src/pages/auth/components/social-auth.tsx +++ b/frontend/app/src/pages/auth/components/social-auth.tsx @@ -4,7 +4,7 @@ import useControlPlane from '@/hooks/use-control-plane.ts'; import { ArrowLeft, LockOpen } from 'lucide-react'; import React, { useState } from 'react'; -export type SocialAuthProvider = 'google' | 'github' | 'sso'; +export type SocialAuthProvider = 'google' | 'github' | 'azure' | 'sso'; const PROVIDER_CONFIG: Record< SocialAuthProvider, @@ -20,6 +20,11 @@ const PROVIDER_CONFIG: Record< label: 'GitHub', icon: , }, + azure: { + href: 'users/azure/start', + label: 'Azure', + icon: , + }, sso: { href: 'users/sso/start', label: 'SSO', diff --git a/frontend/docs/pages/self-hosting/configuration-options.mdx b/frontend/docs/pages/self-hosting/configuration-options.mdx index fedc09a7f5..22ace02929 100644 --- a/frontend/docs/pages/self-hosting/configuration-options.mdx +++ b/frontend/docs/pages/self-hosting/configuration-options.mdx @@ -270,6 +270,11 @@ Variables marked with ⚠️ are conditionally required when specific features a | `SERVER_AUTH_GITHUB_CLIENT_ID` ⚠️ | GitHub auth client ID (required if GitHub auth enabled) | | | `SERVER_AUTH_GITHUB_CLIENT_SECRET` ⚠️ | GitHub auth client secret (required if GitHub auth enabled) | | | `SERVER_AUTH_GITHUB_SCOPES` | GitHub auth scopes | `["read:user", "user:email"]` | +| `SERVER_AUTH_AZURE_ENABLED` | Whether Azure auth is enabled | `false` | +| `SERVER_AUTH_AZURE_CLIENT_ID` ⚠️ | Azure auth client ID (required if Azure auth enabled) | | +| `SERVER_AUTH_AZURE_CLIENT_SECRET` ⚠️ | Azure auth client secret (required if Azure auth enabled) | | +| `SERVER_AUTH_AZURE_TENANT_ID` | Azure AD tenant ID/domain (empty allows any organization) | `organizations` | +| `SERVER_AUTH_AZURE_SCOPES` | Azure auth scopes | `["openid", "profile", "email"]` | ## Task Queue Configuration diff --git a/pkg/auth/oauth/configs.go b/pkg/auth/oauth/configs.go index a0d181238a..1dbbb8be2a 100644 --- a/pkg/auth/oauth/configs.go +++ b/pkg/auth/oauth/configs.go @@ -1,6 +1,8 @@ package oauth import ( + "fmt" + "golang.org/x/oauth2" ) @@ -9,6 +11,12 @@ type Config struct { ClientSecret string Scopes []string BaseURL string + + // TenantID is only used by the Azure AD (Entra ID) provider. It is the + // {tenant} segment of the authorize/token endpoints and may be a specific + // tenant GUID/domain or one of the meta-tenants ("organizations", "common", + // "consumers"). Ignored by the other providers. + TenantID string } const ( @@ -18,6 +26,16 @@ const ( GithubTokenURL string = "https://github.com/login/oauth/access_token" // #nosec G101 SlackAuthURL string = "https://slack.com/oauth/v2/authorize" SlackTokenURL string = "https://slack.com/api/oauth.v2.access" // #nosec G101 + + // AzureADBaseURL is the base for Azure AD (Entra ID) OAuth endpoints. The + // {tenant} segment is interpolated per-instance from the configured tenant. + AzureADBaseURL string = "https://login.microsoftonline.com" + + // DefaultAzureTenant is used when no tenant is configured. It mirrors the + // Google/GitHub providers' behavior: any directory may authenticate, and + // access is instead gated by RestrictedEmailDomains. Set a specific tenant + // ID/domain to lock sign-in to a single Azure AD directory. + DefaultAzureTenant string = "organizations" ) func NewGoogleClient(cfg *Config) *oauth2.Config { @@ -58,3 +76,21 @@ func NewSlackClient(cfg *Config) *oauth2.Config { Scopes: cfg.Scopes, } } + +func NewAzureClient(cfg *Config) *oauth2.Config { + tenant := cfg.TenantID + if tenant == "" { + tenant = DefaultAzureTenant + } + + return &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: fmt.Sprintf("%s/%s/oauth2/v2.0/authorize", AzureADBaseURL, tenant), + TokenURL: fmt.Sprintf("%s/%s/oauth2/v2.0/token", AzureADBaseURL, tenant), + }, + RedirectURL: cfg.BaseURL + "/api/v1/users/azure/callback", + Scopes: cfg.Scopes, + } +} diff --git a/pkg/client/rest/gen.go b/pkg/client/rest/gen.go index 54e744a8fe..2ff93f0632 100644 --- a/pkg/client/rest/gen.go +++ b/pkg/client/rest/gen.go @@ -3950,6 +3950,12 @@ type ClientInterface interface { // WorkflowGetWorkersCount request WorkflowGetWorkersCount(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + // UserUpdateAzureOauthCallback request + UserUpdateAzureOauthCallback(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UserUpdateAzureOauthStart request + UserUpdateAzureOauthStart(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // UserGetCurrent request UserGetCurrent(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -5842,6 +5848,30 @@ func (c *Client) WorkflowGetWorkersCount(ctx context.Context, tenant openapi_typ return c.Client.Do(req) } +func (c *Client) UserUpdateAzureOauthCallback(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUserUpdateAzureOauthCallbackRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UserUpdateAzureOauthStart(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUserUpdateAzureOauthStartRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) UserGetCurrent(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewUserGetCurrentRequest(c.Server) if err != nil { @@ -13491,6 +13521,60 @@ func NewWorkflowGetWorkersCountRequest(server string, tenant openapi_types.UUID, return req, nil } +// NewUserUpdateAzureOauthCallbackRequest generates requests for UserUpdateAzureOauthCallback +func NewUserUpdateAzureOauthCallbackRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/users/azure/callback") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUserUpdateAzureOauthStartRequest generates requests for UserUpdateAzureOauthStart +func NewUserUpdateAzureOauthStartRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/users/azure/start") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewUserGetCurrentRequest generates requests for UserGetCurrent func NewUserGetCurrentRequest(server string) (*http.Request, error) { var err error @@ -14883,6 +14967,12 @@ type ClientWithResponsesInterface interface { // WorkflowGetWorkersCountWithResponse request WorkflowGetWorkersCountWithResponse(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*WorkflowGetWorkersCountResponse, error) + // UserUpdateAzureOauthCallbackWithResponse request + UserUpdateAzureOauthCallbackWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserUpdateAzureOauthCallbackResponse, error) + + // UserUpdateAzureOauthStartWithResponse request + UserUpdateAzureOauthStartWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserUpdateAzureOauthStartResponse, error) + // UserGetCurrentWithResponse request UserGetCurrentWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserGetCurrentResponse, error) @@ -17855,6 +17945,48 @@ func (r WorkflowGetWorkersCountResponse) StatusCode() int { return 0 } +type UserUpdateAzureOauthCallbackResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UserUpdateAzureOauthCallbackResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UserUpdateAzureOauthCallbackResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UserUpdateAzureOauthStartResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UserUpdateAzureOauthStartResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UserUpdateAzureOauthStartResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type UserGetCurrentResponse struct { Body []byte HTTPResponse *http.Response @@ -19770,6 +19902,24 @@ func (c *ClientWithResponses) WorkflowGetWorkersCountWithResponse(ctx context.Co return ParseWorkflowGetWorkersCountResponse(rsp) } +// UserUpdateAzureOauthCallbackWithResponse request returning *UserUpdateAzureOauthCallbackResponse +func (c *ClientWithResponses) UserUpdateAzureOauthCallbackWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserUpdateAzureOauthCallbackResponse, error) { + rsp, err := c.UserUpdateAzureOauthCallback(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUserUpdateAzureOauthCallbackResponse(rsp) +} + +// UserUpdateAzureOauthStartWithResponse request returning *UserUpdateAzureOauthStartResponse +func (c *ClientWithResponses) UserUpdateAzureOauthStartWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserUpdateAzureOauthStartResponse, error) { + rsp, err := c.UserUpdateAzureOauthStart(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUserUpdateAzureOauthStartResponse(rsp) +} + // UserGetCurrentWithResponse request returning *UserGetCurrentResponse func (c *ClientWithResponses) UserGetCurrentWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserGetCurrentResponse, error) { rsp, err := c.UserGetCurrent(ctx, reqEditors...) @@ -25088,6 +25238,38 @@ func ParseWorkflowGetWorkersCountResponse(rsp *http.Response) (*WorkflowGetWorke return response, nil } +// ParseUserUpdateAzureOauthCallbackResponse parses an HTTP response from a UserUpdateAzureOauthCallbackWithResponse call +func ParseUserUpdateAzureOauthCallbackResponse(rsp *http.Response) (*UserUpdateAzureOauthCallbackResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UserUpdateAzureOauthCallbackResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseUserUpdateAzureOauthStartResponse parses an HTTP response from a UserUpdateAzureOauthStartWithResponse call +func ParseUserUpdateAzureOauthStartResponse(rsp *http.Response) (*UserUpdateAzureOauthStartResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UserUpdateAzureOauthStartResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseUserGetCurrentResponse parses an HTTP response from a UserGetCurrentWithResponse call func ParseUserGetCurrentResponse(rsp *http.Response) (*UserGetCurrentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/config/loader/loader.go b/pkg/config/loader/loader.go index 96f079eb79..c4c33d7101 100644 --- a/pkg/config/loader/loader.go +++ b/pkg/config/loader/loader.go @@ -569,6 +569,9 @@ func createControllerLayer(dc *database.Layer, cf *server.ServerConfigFile, vers if cf.Auth.Github.Enabled { oauthProviders = append(oauthProviders, "github") } + if cf.Auth.Azure.Enabled { + oauthProviders = append(oauthProviders, "azure") + } securityCheck := security.NewSecurityCheck(&security.DefaultSecurityCheck{ Enabled: cf.SecurityCheck.Enabled, @@ -698,6 +701,24 @@ func createControllerLayer(dc *database.Layer, cf *server.ServerConfigFile, vers }) } + if cf.Auth.Azure.Enabled { + if cf.Auth.Azure.ClientID == "" { + return nil, nil, fmt.Errorf("azure client id is required") + } + + if cf.Auth.Azure.ClientSecret == "" { + return nil, nil, fmt.Errorf("azure client secret is required") + } + + auth.AzureOAuthConfig = oauth.NewAzureClient(&oauth.Config{ + ClientID: cf.Auth.Azure.ClientID, + ClientSecret: cf.Auth.Azure.ClientSecret, + BaseURL: cf.Runtime.ServerURL, + TenantID: cf.Auth.Azure.TenantID, + Scopes: cf.Auth.Azure.Scopes, + }) + } + encryptionSvc, err := LoadEncryptionSvc(cf) if err != nil { diff --git a/pkg/config/server/server.go b/pkg/config/server/server.go index 38dd6587f9..57bafb909c 100644 --- a/pkg/config/server/server.go +++ b/pkg/config/server/server.go @@ -467,6 +467,8 @@ type ConfigFileAuth struct { Github ConfigFileAuthGithub `mapstructure:"github" json:"github,omitempty"` + Azure ConfigFileAuthAzure `mapstructure:"azure" json:"azure,omitempty"` + ControlPlaneExchangeTokenConfig ConfigFileAuthControlPlaneExchangeToken `mapstructure:"controlPlaneExchangeToken" json:"controlPlaneExchangeToken,omitempty"` } @@ -520,6 +522,20 @@ type ConfigFileAuthGithub struct { Scopes []string `mapstructure:"scopes" json:"scopes,omitempty" default:"[\"read:user\", \"user:email\"]"` } +type ConfigFileAuthAzure struct { + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty" default:"false"` + + ClientID string `mapstructure:"clientID" json:"clientID,omitempty"` + ClientSecret string `mapstructure:"clientSecret" json:"clientSecret,omitempty"` + + // TenantID is the Azure AD (Entra ID) tenant to authenticate against. Leave + // empty to allow any organizational directory (gated by RestrictedEmailDomains), + // or set a specific tenant GUID/domain to lock sign-in to a single directory. + TenantID string `mapstructure:"tenantID" json:"tenantID,omitempty"` + + Scopes []string `mapstructure:"scopes" json:"scopes,omitempty" default:"[\"openid\", \"profile\", \"email\"]"` +} + type ConfigFileAuthCookie struct { Name string `mapstructure:"name" json:"name,omitempty" default:"hatchet"` Domain string `mapstructure:"domain" json:"domain,omitempty"` @@ -617,6 +633,8 @@ type AuthConfig struct { GithubOAuthConfig *oauth2.Config + AzureOAuthConfig *oauth2.Config + JWTManager token.JWTManager ExchangeTokenClient exchangetoken.ExchangeTokenClient @@ -873,6 +891,11 @@ func BindAllEnv(v *viper.Viper) { _ = v.BindEnv("auth.github.clientID", "SERVER_AUTH_GITHUB_CLIENT_ID") _ = v.BindEnv("auth.github.clientSecret", "SERVER_AUTH_GITHUB_CLIENT_SECRET") _ = v.BindEnv("auth.github.scopes", "SERVER_AUTH_GITHUB_SCOPES") + _ = v.BindEnv("auth.azure.enabled", "SERVER_AUTH_AZURE_ENABLED") + _ = v.BindEnv("auth.azure.clientID", "SERVER_AUTH_AZURE_CLIENT_ID") + _ = v.BindEnv("auth.azure.clientSecret", "SERVER_AUTH_AZURE_CLIENT_SECRET") + _ = v.BindEnv("auth.azure.tenantID", "SERVER_AUTH_AZURE_TENANT_ID") + _ = v.BindEnv("auth.azure.scopes", "SERVER_AUTH_AZURE_SCOPES") // task queue options // legacy options diff --git a/pkg/repository/user.go b/pkg/repository/user.go index ea09e69287..0c05512ddc 100644 --- a/pkg/repository/user.go +++ b/pkg/repository/user.go @@ -24,7 +24,7 @@ type CreateUserOpts struct { } type OAuthOpts struct { - Provider string `validate:"required,oneof=google github sso"` + Provider string `validate:"required,oneof=google github sso azure"` ProviderUserId string `validate:"required,min=1"` AccessToken []byte `validate:"required,min=1"` RefreshToken []byte // optional