From c5fc4b4f99034dcf4f3dd1374ac5ef8455b54d5f Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 9 Dec 2025 08:32:05 +0100 Subject: [PATCH] Cardata: display oauth user code (#25818) --- api/api.go | 4 +- assets/css/app.css | 26 ++- .../js/components/Config/AuthCodeDisplay.vue | 53 +++++ .../components/Config/AuthConnectButton.vue | 50 +++++ .../components/Config/AuthProvidersCard.vue | 100 +++++++++ .../components/Config/AuthSuccessBanner.vue | 5 +- assets/js/components/Config/DeviceCard.vue | 11 +- .../Config/DeviceModal/DeviceModalBase.vue | 101 ++++----- .../js/components/Config/DeviceModal/index.ts | 24 +- .../components/Config/utils/authProvider.ts | 103 +++++++++ assets/js/components/Helper/CopyLink.vue | 46 ++++ assets/js/components/MaterialIcon/Key.vue | 18 ++ assets/js/components/Site/Site.vue | 18 +- .../js/components/Top/AuthProviderModal.vue | 207 ++++++++++++++++++ assets/js/components/Top/Header.vue | 22 +- assets/js/components/Top/Navigation.vue | 75 ++----- .../js/components/Top/TopNavigationArea.vue | 81 +++++++ assets/js/components/Top/types.d.ts | 3 +- assets/js/utils/clipboard.ts | 29 +++ assets/js/views/Config.vue | 22 +- assets/js/views/Sessions.vue | 4 +- i18n/de.json | 26 ++- i18n/en.json | 26 ++- plugin/auth/demo.go | 53 ++++- plugin/auth/oauth.go | 10 +- server/providerauth/handler.go | 22 +- tests/config-device-auth-demo.tpl.yaml | 10 +- tests/config-device-auth.spec.ts | 64 +++++- 28 files changed, 993 insertions(+), 220 deletions(-) create mode 100644 assets/js/components/Config/AuthCodeDisplay.vue create mode 100644 assets/js/components/Config/AuthConnectButton.vue create mode 100644 assets/js/components/Config/AuthProvidersCard.vue create mode 100644 assets/js/components/Config/utils/authProvider.ts create mode 100644 assets/js/components/Helper/CopyLink.vue create mode 100644 assets/js/components/MaterialIcon/Key.vue create mode 100644 assets/js/components/Top/AuthProviderModal.vue create mode 100644 assets/js/components/Top/TopNavigationArea.vue create mode 100644 assets/js/utils/clipboard.ts diff --git a/api/api.go b/api/api.go index abd0988f1..34c212717 100644 --- a/api/api.go +++ b/api/api.go @@ -5,6 +5,8 @@ import ( "io" "net/url" "time" + + "golang.org/x/oauth2" ) //go:generate go tool mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Dimmer,Tariff @@ -214,7 +216,7 @@ type Tariff interface { // AuthProvider is the ability to provide OAuth authentication through the ui type AuthProvider interface { - Login(state string) (string, error) + Login(state string) (string, *oauth2.DeviceAuthResponse, error) Logout() error HandleCallback(params url.Values) error Authenticated() bool diff --git a/assets/css/app.css b/assets/css/app.css index 510d802aa..9d77cd105 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -564,8 +564,12 @@ input::-webkit-datetime-edit { } input.form-control:disabled, input.form-control:read-only, +textarea.form-control:disabled, +textarea.form-control:read-only, .dark input.form-control:disabled, -.dark input.form-control:read-only { +.dark input.form-control:read-only, +.dark textarea.form-control:disabled, +.dark textarea.form-control:read-only { background-color: var(--evcc-gray-10); color: var(--bs-default-text); opacity: 1; @@ -701,11 +705,29 @@ html.app .modal-dialog { } .dark .round-box--error { + box-shadow: 0 0 0.5rem var(--bs-danger); + border: 1px solid var(--bs-danger); +} + +.dark .round-box--error .tags * { + color: var(--bs-danger) !important; +} + +.round-box--warning { box-shadow: 0 0 0.5rem var(--bs-warning); border: 1px solid var(--bs-warning); } -.dark .round-box--error .tags * { +.round-box--warning .tags * { + color: var(--bs-warning-text-emphasis) !important; +} + +.dark .round-box--warning { + box-shadow: 0 0 0.5rem var(--bs-warning); + border: 1px solid var(--bs-warning); +} + +.dark .round-box--warning .tags * { color: var(--bs-warning) !important; } diff --git a/assets/js/components/Config/AuthCodeDisplay.vue b/assets/js/components/Config/AuthCodeDisplay.vue new file mode 100644 index 000000000..9c757ff56 --- /dev/null +++ b/assets/js/components/Config/AuthCodeDisplay.vue @@ -0,0 +1,53 @@ + + + diff --git a/assets/js/components/Config/AuthConnectButton.vue b/assets/js/components/Config/AuthConnectButton.vue new file mode 100644 index 000000000..98f1beced --- /dev/null +++ b/assets/js/components/Config/AuthConnectButton.vue @@ -0,0 +1,50 @@ + + + diff --git a/assets/js/components/Config/AuthProvidersCard.vue b/assets/js/components/Config/AuthProvidersCard.vue new file mode 100644 index 000000000..2f6fe98fa --- /dev/null +++ b/assets/js/components/Config/AuthProvidersCard.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/assets/js/components/Config/AuthSuccessBanner.vue b/assets/js/components/Config/AuthSuccessBanner.vue index 0048fc5ef..b0363e1de 100644 --- a/assets/js/components/Config/AuthSuccessBanner.vue +++ b/assets/js/components/Config/AuthSuccessBanner.vue @@ -1,6 +1,9 @@ diff --git a/assets/js/components/Config/DeviceCard.vue b/assets/js/components/Config/DeviceCard.vue index f9fe24419..9aa82e1c4 100644 --- a/assets/js/components/Config/DeviceCard.vue +++ b/assets/js/components/Config/DeviceCard.vue @@ -1,5 +1,8 @@ diff --git a/assets/js/components/Top/types.d.ts b/assets/js/components/Top/types.d.ts index 92494593d..68d55b2e5 100644 --- a/assets/js/components/Top/types.d.ts +++ b/assets/js/components/Top/types.d.ts @@ -1,6 +1,5 @@ export interface Provider { title: string; authenticated: boolean; - loginPath: string; - logoutPath: string; + id: string; } diff --git a/assets/js/utils/clipboard.ts b/assets/js/utils/clipboard.ts new file mode 100644 index 000000000..e9ff7c987 --- /dev/null +++ b/assets/js/utils/clipboard.ts @@ -0,0 +1,29 @@ +export function isClipboardSupported(): boolean { + return !!(navigator.clipboard && window.isSecureContext); +} + +export async function copyToClipboard(text: string): Promise { + if (!isClipboardSupported()) { + return false; + } + try { + await navigator.clipboard.writeText(text); + return true; + } catch (err) { + console.error("Failed to copy to clipboard:", err); + return false; + } +} + +export async function copyWithFeedback( + text: string, + setCopiedState: (value: boolean) => void +): Promise { + const success = await copyToClipboard(text); + if (success) { + setCopiedState(true); + setTimeout(() => { + setCopiedState(false); + }, 2000); + } +} diff --git a/assets/js/views/Config.vue b/assets/js/views/Config.vue index 30c85804e..401b8b9c5 100644 --- a/assets/js/views/Config.vue +++ b/assets/js/views/Config.vue @@ -1,7 +1,11 @@