mirror of
https://github.com/langgenius/dify.git
synced 2026-05-21 01:07:03 +08:00
Adds a CLI-friendly authorization flow so difyctl (and future
non-browser clients) can obtain user-scoped tokens without copy-
pasting cookies or raw API keys. Two grant paths share one device
flow surface:
1. Account branch — user signs in via the existing /signin
methods, /device page calls console-authed approve, mints a
dfoa_ token tied to (account_id, tenant).
2. External-SSO branch (EE) — /v1/oauth/device/sso-initiate signs
an SSOState envelope, hands off to Enterprise's external ACS,
receives a signed external-subject assertion, mints a dfoe_
token tied to (subject_email, subject_issuer).
API surface (all under /v1, EE-only endpoints 404 on CE):
POST /v1/oauth/device/code — RFC 8628 start
POST /v1/oauth/device/token — RFC 8628 poll
GET /v1/oauth/device/lookup — pre-validate user_code
GET /v1/oauth/device/sso-initiate — SSO branch entry
GET /v1/device/sso-complete — SSO callback sink
GET /v1/oauth/device/approval-context — /device cookie probe
POST /v1/oauth/device/approve-external — SSO approve
GET /v1/me — bearer subject lookup
DELETE /v1/oauth/authorizations/self — self-revoke
POST /console/api/oauth/device/approve — account approve
POST /console/api/oauth/device/deny — account deny
Core primitives:
- libs/oauth_bearer.py: prefix-keyed TokenKindRegistry +
BearerAuthenticator + validate_bearer decorator. Two-tier scope
(full vs apps:run) stamped from the registry, never from the DB.
- libs/jws.py: HS256 compact JWS keyed on the shared Dify
SECRET_KEY — same key-set verifies the SSOState envelope, the
external-subject assertion (minted by Enterprise), and the
approval-grant cookie.
- libs/device_flow_security.py: enterprise_only gate, approval-
grant cookie mint/verify/consume (Path=/v1/oauth/device,
HttpOnly, SameSite=Lax, Secure follows is_secure()), anti-
framing headers.
- libs/rate_limit.py: typed RateLimit / RateLimitScope dispatch
with composite-key buckets; both decorator + imperative form.
- services/oauth_device_flow.py: Redis state machine (PENDING ->
APPROVED|DENIED with atomic consume-on-poll), token mint via
partial unique index uq_oauth_active_per_device (rotates in
place), env-driven TTL policy.
Storage: oauth_access_tokens table with partial unique index on
(subject_email, subject_issuer, client_id, device_label) WHERE
revoked_at IS NULL. account_id NULL distinguishes external-SSO
rows. Hard-expire is CAS UPDATE (revoked_at + nullify token_hash)
so audit events keep their token_id. Retention pruner DELETEs
revoked + zombie-expired rows past OAUTH_ACCESS_TOKEN_RETENTION_DAYS.
Frontend: /device page with code-entry, chooser (account vs SSO),
authorize-account, authorize-sso views. SSO branch detaches from
the URL user_code and reads everything from the cookie via
/approval-context. Anti-framing headers on all responses.
Wiring: ENABLE_OAUTH_BEARER feature flag; ext_oauth_bearer binds
the authenticator at startup; clean_oauth_access_tokens_task
scheduled in ext_celery.
Spec: docs/specs/v1.0/server/{device-flow,tokens,middleware,security}.md
226 lines
4.4 KiB
Python
226 lines
4.4 KiB
Python
from importlib import import_module
|
|
|
|
from flask import Blueprint
|
|
from flask_restx import Namespace
|
|
|
|
from libs.external_api import ExternalApi
|
|
|
|
bp = Blueprint("console", __name__, url_prefix="/console/api")
|
|
|
|
api = ExternalApi(
|
|
bp,
|
|
version="1.0",
|
|
title="Console API",
|
|
description="Console management APIs for app configuration, monitoring, and administration",
|
|
)
|
|
|
|
console_ns = Namespace("console", description="Console management API operations", path="/")
|
|
|
|
RESOURCE_MODULES = (
|
|
"controllers.console.app.app_import",
|
|
"controllers.console.explore.audio",
|
|
"controllers.console.explore.completion",
|
|
"controllers.console.explore.conversation",
|
|
"controllers.console.explore.message",
|
|
"controllers.console.explore.workflow",
|
|
"controllers.console.files",
|
|
"controllers.console.remote_files",
|
|
)
|
|
|
|
for module_name in RESOURCE_MODULES:
|
|
import_module(module_name)
|
|
|
|
# Ensure resource modules are imported so route decorators are evaluated.
|
|
# Import other controllers
|
|
from . import (
|
|
admin,
|
|
apikey,
|
|
extension,
|
|
feature,
|
|
human_input_form,
|
|
init_validate,
|
|
notification,
|
|
ping,
|
|
setup,
|
|
spec,
|
|
version,
|
|
)
|
|
|
|
# Import app controllers
|
|
from .app import (
|
|
advanced_prompt_template,
|
|
agent,
|
|
annotation,
|
|
app,
|
|
audio,
|
|
completion,
|
|
conversation,
|
|
conversation_variables,
|
|
generator,
|
|
mcp_server,
|
|
message,
|
|
model_config,
|
|
ops_trace,
|
|
site,
|
|
statistic,
|
|
workflow,
|
|
workflow_app_log,
|
|
workflow_comment,
|
|
workflow_draft_variable,
|
|
workflow_run,
|
|
workflow_statistic,
|
|
workflow_trigger,
|
|
)
|
|
|
|
# Import auth controllers
|
|
from .auth import (
|
|
activate,
|
|
data_source_bearer_auth,
|
|
data_source_oauth,
|
|
email_register,
|
|
forgot_password,
|
|
login,
|
|
oauth,
|
|
oauth_device,
|
|
oauth_server,
|
|
)
|
|
|
|
# Import billing controllers
|
|
from .billing import billing, compliance
|
|
|
|
# Import datasets controllers
|
|
from .datasets import (
|
|
data_source,
|
|
datasets,
|
|
datasets_document,
|
|
datasets_segments,
|
|
external,
|
|
hit_testing,
|
|
metadata,
|
|
website,
|
|
)
|
|
from .datasets.rag_pipeline import (
|
|
datasource_auth,
|
|
datasource_content_preview,
|
|
rag_pipeline,
|
|
rag_pipeline_datasets,
|
|
rag_pipeline_draft_variable,
|
|
rag_pipeline_import,
|
|
rag_pipeline_workflow,
|
|
)
|
|
|
|
# Import explore controllers
|
|
from .explore import (
|
|
banner,
|
|
installed_app,
|
|
parameter,
|
|
recommended_app,
|
|
saved_message,
|
|
trial,
|
|
)
|
|
from .socketio import workflow as socketio_workflow # pyright: ignore[reportUnusedImport]
|
|
|
|
# Import tag controllers
|
|
from .tag import tags
|
|
|
|
# Import workspace controllers
|
|
from .workspace import (
|
|
account,
|
|
agent_providers,
|
|
endpoint,
|
|
load_balancing_config,
|
|
members,
|
|
model_providers,
|
|
models,
|
|
plugin,
|
|
tool_providers,
|
|
trigger_providers,
|
|
workspace,
|
|
)
|
|
|
|
api.add_namespace(console_ns)
|
|
|
|
__all__ = [
|
|
"account",
|
|
"activate",
|
|
"admin",
|
|
"advanced_prompt_template",
|
|
"agent",
|
|
"agent_providers",
|
|
"annotation",
|
|
"api",
|
|
"apikey",
|
|
"app",
|
|
"audio",
|
|
"banner",
|
|
"billing",
|
|
"bp",
|
|
"completion",
|
|
"compliance",
|
|
"console_ns",
|
|
"conversation",
|
|
"conversation_variables",
|
|
"data_source",
|
|
"data_source_bearer_auth",
|
|
"data_source_oauth",
|
|
"datasets",
|
|
"datasets_document",
|
|
"datasets_segments",
|
|
"datasource_auth",
|
|
"datasource_content_preview",
|
|
"email_register",
|
|
"endpoint",
|
|
"extension",
|
|
"external",
|
|
"feature",
|
|
"forgot_password",
|
|
"generator",
|
|
"hit_testing",
|
|
"human_input_form",
|
|
"init_validate",
|
|
"installed_app",
|
|
"load_balancing_config",
|
|
"login",
|
|
"mcp_server",
|
|
"members",
|
|
"message",
|
|
"metadata",
|
|
"model_config",
|
|
"model_providers",
|
|
"models",
|
|
"notification",
|
|
"oauth",
|
|
"oauth_device",
|
|
"oauth_server",
|
|
"ops_trace",
|
|
"parameter",
|
|
"ping",
|
|
"plugin",
|
|
"rag_pipeline",
|
|
"rag_pipeline_datasets",
|
|
"rag_pipeline_draft_variable",
|
|
"rag_pipeline_import",
|
|
"rag_pipeline_workflow",
|
|
"recommended_app",
|
|
"saved_message",
|
|
"setup",
|
|
"site",
|
|
"socketio_workflow",
|
|
"spec",
|
|
"statistic",
|
|
"tags",
|
|
"tool_providers",
|
|
"trial",
|
|
"trigger_providers",
|
|
"version",
|
|
"website",
|
|
"workflow",
|
|
"workflow_app_log",
|
|
"workflow_comment",
|
|
"workflow_draft_variable",
|
|
"workflow_run",
|
|
"workflow_statistic",
|
|
"workflow_trigger",
|
|
"workspace",
|
|
]
|