From 89bd0a7605dedccd1e0c6f00727c7f7ecceae464 Mon Sep 17 00:00:00 2001 From: Mathieu Leplatre Date: Tue, 28 May 2019 13:14:42 +0200 Subject: [PATCH] Fix #915: Add button to copy authentication header --- css/styles.css | 2 +- src/actions/session.js | 7 +++++++ src/client.js | 6 ++++-- src/components/App.js | 25 ++++++++++++++++++++----- src/constants.js | 2 ++ src/sagas/index.js | 5 +++++ src/sagas/session.js | 20 ++++++++++++++++++-- src/utils.js | 12 ++++++++++++ 8 files changed, 69 insertions(+), 10 deletions(-) diff --git a/css/styles.css b/css/styles.css index 5a16578db..d7a746be6 100644 --- a/css/styles.css +++ b/css/styles.css @@ -70,7 +70,7 @@ h1 { padding-top: .3em; } -.user-info a { +.user-info a.spaced { margin-left: 1em; } diff --git a/src/actions/session.js b/src/actions/session.js index bb5ac135a..302030e2b 100644 --- a/src/actions/session.js +++ b/src/actions/session.js @@ -15,6 +15,7 @@ import { SESSION_BUCKETS_REQUEST, SESSION_BUCKETS_SUCCESS, SESSION_AUTHENTICATED, + SESSION_COPY_AUTHENTICATION_HEADER, SESSION_LOGOUT, } from "../constants"; @@ -110,6 +111,12 @@ export function setAuthenticated(): { return { type: SESSION_AUTHENTICATED }; } +export function copyAuthenticationHeader(): { + type: "SESSION_COPY_AUTHENTICATION_HEADER", +} { + return { type: SESSION_COPY_AUTHENTICATION_HEADER }; +} + export function logout(): { type: "SESSION_LOGOUT", } { diff --git a/src/client.js b/src/client.js index fd4aad680..a484f2c32 100644 --- a/src/client.js +++ b/src/client.js @@ -6,8 +6,10 @@ import KintoClient from "kinto-http"; let client: ?KintoClient; -function getAuthHeader(auth: AuthData): ?string { - switch (auth.authType) { +export function getAuthHeader(auth: AuthData): ?string { + // For openid, `authType` can be suffixed (eg. `"openid-auth0"`). + const authType = auth.authType.split("-")[0]; + switch (authType) { case "fxa": { const { token } = auth.credentials; return `Bearer ${token}`; diff --git a/src/components/App.js b/src/components/App.js index 3561030e3..7dbc428c3 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -47,7 +47,7 @@ function UserInfo({ session }) { ); } -function SessionInfoBar({ session, logout }) { +function SessionInfoBar({ session, logout, copyAuthenticationHeader }) { const { serverInfo: { url, project_name, project_docs }, } = session; @@ -55,16 +55,26 @@ function SessionInfoBar({ session, logout }) {

{project_name}

- on {url}{" "} + + on {url}{" "} + + event.preventDefault() || copyAuthenticationHeader() + }> + + + className="spaced btn btn-xs btn-default project-docs"> Documentation event.preventDefault() || logout()}> Logout @@ -94,6 +104,7 @@ export type Props = { export default class App extends PureComponent { render() { const { + copyAuthenticationHeader, session, logout, notificationList, @@ -111,7 +122,11 @@ export default class App extends PureComponent { return (
{session.authenticated && ( - + )}
diff --git a/src/constants.js b/src/constants.js index fce9f7de7..e2f0bd440 100644 --- a/src/constants.js +++ b/src/constants.js @@ -65,6 +65,8 @@ export const SESSION_AUTHENTICATED = "SESSION_AUTHENTICATED"; export const SESSION_BUCKETS_REQUEST = "SESSION_BUCKETS_REQUEST"; export const SESSION_BUCKETS_SUCCESS = "SESSION_BUCKETS_SUCCESS"; export const SESSION_BUSY = "SESSION_BUSY"; +export const SESSION_COPY_AUTHENTICATION_HEADER = + "SESSION_COPY_AUTHENTICATION_HEADER"; export const SESSION_LOGOUT = "SESSION_LOGOUT"; export const SESSION_SERVER_CHANGE = "SESSION_SERVER_CHANGE"; export const SESSION_GET_SERVERINFO = "SESSION_GET_SERVERINFO"; diff --git a/src/sagas/index.js b/src/sagas/index.js index 88684749b..d049ec3d7 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -29,6 +29,11 @@ export default function* rootSaga( takeEvery(c.SESSION_GET_SERVERINFO, sessionSagas.getServerInfo, getState), takeEvery(c.SESSION_BUCKETS_REQUEST, sessionSagas.listBuckets, getState), takeEvery(c.SESSION_LOGOUT, sessionSagas.sessionLogout, getState), + takeEvery( + c.SESSION_COPY_AUTHENTICATION_HEADER, + sessionSagas.sessionCopyAuthenticationHeader, + getState + ), // route takeEvery(c.ROUTE_REDIRECT, routeSagas.routeRedirect, getState), takeEvery(c.ROUTE_UPDATED, routeSagas.routeUpdated, getState), diff --git a/src/sagas/session.js b/src/sagas/session.js index 22a4d6d9e..25a40d64d 100644 --- a/src/sagas/session.js +++ b/src/sagas/session.js @@ -18,8 +18,8 @@ import * as notificationActions from "../actions/notifications"; import * as actions from "../actions/session"; import * as historyActions from "../actions/history"; import { DEFAULT_SERVERINFO } from "../reducers/session"; -import { clone, getAuthLabel } from "../utils"; -import { getClient, setupClient, resetClient } from "../client"; +import { clone, getAuthLabel, copyToClipboard } from "../utils"; +import { getClient, setupClient, resetClient, getAuthHeader } from "../client"; export function* serverChange(): SagaGen { yield put(actions.serverInfoSuccess(DEFAULT_SERVERINFO)); @@ -161,6 +161,22 @@ export function* sessionLogout( yield call(clearSession); } +export function* sessionCopyAuthenticationHeader( + getState: GetStateFn, + action: ActionType +): SagaGen { + const { + session: { auth }, + } = getState(); + const authHeader = getAuthHeader(auth); + yield call(copyToClipboard, authHeader); + yield put( + notificationActions.notifySuccess("Header copied to clipboard", { + persistent: false, + }) + ); +} + export function expandBucketsCollections( buckets: BucketEntry[], permissions: PermissionEntry[] diff --git a/src/utils.js b/src/utils.js index 6f8f16d82..af1c0bbb0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -343,3 +343,15 @@ export function parseHistoryFilters(s: string): HistoryFilters { } return ret; } + +/** + * Copy specified string to OS clipboard. + */ +export async function copyToClipboard(s: string) { + const { state } = await navigator.permissions.query({ + name: "clipboard-write", + }); + if (state == "granted" || state == "prompt") { + await navigator.clipboard.writeText(s); + } +}