Skip to content

Commit

Permalink
scaffolder hooks to call auth and execute a fetch with a token
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolda committed Feb 15, 2023
1 parent 9ed186a commit 1324ee3
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/scaffolder-frontend-auth/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
12 changes: 12 additions & 0 deletions plugins/scaffolder-frontend-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# scaffolder-frontend-auth

_This package was created through the Backstage CLI_.

## Installation

Install the package via Yarn:

```sh
cd <package-dir> # if within a monorepo
yarn add scaffolder-frontend-auth
```
41 changes: 41 additions & 0 deletions plugins/scaffolder-frontend-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "scaffolder-frontend-auth",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"files": [
"dist"
],
"backstage": {
"role": "web-library"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-plugin-api": "^1.3.0",
"@backstage/integration-react": "^1.1.9",
"@backstage/plugin-scaffolder-react": "^1.0.1",
"react-use": "^17.4.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.22.1",
"@testing-library/jest-dom": "^5.10.1"
}
}
46 changes: 46 additions & 0 deletions plugins/scaffolder-frontend-auth/src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from 'react';
import { useApi } from '@backstage/core-plugin-api';
import { scmAuthApiRef } from '@backstage/integration-react';
import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react';
import useDebounce from 'react-use/lib/useDebounce';

export type RequestUserCredentials = {
additionalScopes: Record<string, string[]>;
secretsKey: string;
};

export const useAuth = ({
requestUserCredentials,
url,
}: {
requestUserCredentials?: RequestUserCredentials;
url?: string;
} = {}) => {
const schAuthApi = useApi(scmAuthApiRef);
const { setSecrets } = useTemplateSecrets();
const [localToken, setToken] = useState<string | undefined>();

useDebounce(
async () => {
if (!requestUserCredentials || !url) return;

const { token } = await schAuthApi.getCredentials({
url,
additionalScope: {
repoWrite: true,
...(requestUserCredentials?.additionalScopes
? { customScopes: requestUserCredentials.additionalScopes }
: {}),
},
});

if (requestUserCredentials?.secretsKey)
setSecrets({ [requestUserCredentials.secretsKey]: token });
setToken(token);
},
500,
[localToken],
);

return localToken ? localToken : undefined;
};
70 changes: 70 additions & 0 deletions plugins/scaffolder-frontend-auth/src/hooks/useFetchWithAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import useAsync from 'react-use/lib/useAsync';
import { useAuth } from './useAuth';

export type RequestUserCredentials = {
additionalScopes: Record<string, string[]>;
secretsKey: string;
};

export const useFetchWithAuth = ({
requestUserCredentials,
url,
fetchOpts,
}: {
requestUserCredentials?: RequestUserCredentials;
url: string;
fetchOpts: {
url: string;
options?: Record<string, unknown>;
headersRequiringToken?: string[];
};
}) => {
const token = useAuth({ url, requestUserCredentials });

const { value, loading, error } = useAsync(async (): Promise<any> => {
if (token) {
const headersRequiringToken = fetchOpts?.headersRequiringToken ?? [];
const options = fetchOpts.options ?? {};
if (headersRequiringToken) {
if (!options.headers) options.headers = {} as Record<string, string>;
// add token to any existing headers
options.headers = Object.entries(
options.headers as Record<string, string>,
).reduce((headers, [header, headerValue]) => {
if (headersRequiringToken.includes(header)) {
headers[header] = `${headerValue}${token}`;
} else {
headers[header] = headerValue;
}
return headers;
}, {} as Record<string, string>);

headersRequiringToken.forEach(header => {
if (
!Object.keys(options.headers as Record<string, string>).includes(
header,
)
) {
(options.headers as Record<string, string>)[header] = token;
}
});
}
const response = await fetch(fetchOpts.url, {
...fetchOpts.options,
...headersRequiringToken.reduce((headers, headerVal) => {
headers[headerVal] = token;
return headers;
}, {} as Record<string, string>),
});

if (!response.ok) {
throw new Error(`unable to fetch from ${fetchOpts.url}`);
}

return response.json();
}
return undefined;
}, [token]);

return { value, loading, error };
};
2 changes: 2 additions & 0 deletions plugins/scaffolder-frontend-auth/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useAuth, type RequestUserCredentials } from './hooks/useAuth';
export { useFetchWithAuth } from './hooks/useFetchWithAuth';
1 change: 1 addition & 0 deletions plugins/scaffolder-frontend-auth/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom';

0 comments on commit 1324ee3

Please sign in to comment.