diff --git a/.changeset/dry-scissors-dream.md b/.changeset/dry-scissors-dream.md
new file mode 100644
index 0000000000..09965f0f59
--- /dev/null
+++ b/.changeset/dry-scissors-dream.md
@@ -0,0 +1,5 @@
+---
+'@backstage/integration': minor
+---
+
+added the possibility to handle raw Gitlab URLs with nested namespaces
diff --git a/.changeset/fresh-beans-call.md b/.changeset/fresh-beans-call.md
new file mode 100644
index 0000000000..7327f9a3c9
--- /dev/null
+++ b/.changeset/fresh-beans-call.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Add ability to customize `Read More` destination with `readMoreUrl` prop for `MissingAnnotationEmptyState` component.
diff --git a/.changeset/good-beans-knock.md b/.changeset/good-beans-knock.md
new file mode 100644
index 0000000000..91de53c128
--- /dev/null
+++ b/.changeset/good-beans-knock.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Don't set the background color on an Avatar component that has a picture.
diff --git a/.changeset/lucky-masks-talk.md b/.changeset/lucky-masks-talk.md
new file mode 100644
index 0000000000..e15bfafcc2
--- /dev/null
+++ b/.changeset/lucky-masks-talk.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-module-addons-contrib': patch
+---
+
+Improved inline/type documentation for the addon.
diff --git a/.changeset/nervous-bears-brush.md b/.changeset/nervous-bears-brush.md
new file mode 100644
index 0000000000..f3b0c7db78
--- /dev/null
+++ b/.changeset/nervous-bears-brush.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+The Auth0 adapter no longer disables session refreshing.
diff --git a/.changeset/olive-eggs-accept.md b/.changeset/olive-eggs-accept.md
new file mode 100644
index 0000000000..66d44d9c3e
--- /dev/null
+++ b/.changeset/olive-eggs-accept.md
@@ -0,0 +1,16 @@
+---
+'@backstage/backend-common': patch
+---
+
+**DEPRECATION**: Server-to-server authentication tokens issued from a
+`TokenManager` (specifically, `ServerTokenManager`) now has an expiry time set,
+for one hour in the future from when issued. This improves security. The ability
+to pass in and validate tokens that either have a missing `exp` claim, or an
+`exp` claim that expired in the past, is now deprecated. Trying to do so will
+lead to logged warnings, and in a future release will instead lead to errors.
+
+It was always the case that users of this interface were expected to call its
+`getToken()` method for every outgoing call and never hold on to any given token
+for reuse. But this now has become even more important advice to heed, and you
+should verify that you do not hold on to and reuse tokens such as these in your
+own code.
diff --git a/.changeset/pre.json b/.changeset/pre.json
index e57d86a249..dff929aeec 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -1,5 +1,5 @@
{
- "mode": "pre",
+ "mode": "exit",
"tag": "next",
"initialVersions": {
"example-app": "0.2.70",
@@ -185,11 +185,13 @@
"fair-pigs-mate",
"fast-stingrays-guess",
"fluffy-poems-sell",
+ "fluffy-sloths-deliver",
"fresh-panthers-arrive",
"funny-lamps-turn",
"fuzzy-seahorses-teach",
"gentle-penguins-kick",
"giant-cheetahs-thank",
+ "good-beans-knock",
"grumpy-panthers-peel",
"hip-adults-collect",
"hip-monkeys-hide",
@@ -227,6 +229,7 @@
"renovate-759d9d5",
"renovate-9466904",
"renovate-abcbd6e",
+ "renovate-abcbd6e2",
"renovate-b063c4b",
"renovate-bf43c44",
"renovate-bf63f74",
@@ -237,7 +240,9 @@
"search-the-worst-of-you",
"serious-apricots-collect",
"seven-deers-rule",
+ "seven-panthers-pump",
"shiny-apes-design",
+ "shiny-moles-study",
"shiny-students-approve",
"short-chicken-act",
"short-dodos-sparkle",
@@ -249,6 +254,7 @@
"smart-ghosts-search",
"spotty-plums-rule",
"stale-pigs-reply",
+ "stale-pugs-kiss",
"strong-mangos-sell",
"swift-bugs-share",
"swift-parrots-hammer",
@@ -275,6 +281,7 @@
"wicked-vans-press",
"wild-pigs-work",
"wise-countries-watch",
+ "wise-doors-hammer",
"wise-emus-wait",
"witty-lions-reply"
]
diff --git a/.changeset/pretty-hornets-listen.md b/.changeset/pretty-hornets-listen.md
new file mode 100644
index 0000000000..6e0701e617
--- /dev/null
+++ b/.changeset/pretty-hornets-listen.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search-backend-module-elasticsearch': patch
+---
+
+Fix issue where `nextPageCursor` is defined on the last page of results
diff --git a/.changeset/real-lions-crash.md b/.changeset/real-lions-crash.md
new file mode 100644
index 0000000000..f3087a1bc0
--- /dev/null
+++ b/.changeset/real-lions-crash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Update the rendering of links in the entity inspector so that values starting with `https?://` are rendered as links as well.
diff --git a/.changeset/renovate-9008399.md b/.changeset/renovate-9008399.md
new file mode 100644
index 0000000000..64e1dd07a8
--- /dev/null
+++ b/.changeset/renovate-9008399.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-codescene': patch
+---
+
+Updated dependency `@testing-library/user-event` to `^14.0.0`.
diff --git a/.changeset/renovate-abcbd6e.md b/.changeset/renovate-abcbd6e.md
deleted file mode 100644
index e96d9ea875..0000000000
--- a/.changeset/renovate-abcbd6e.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
-'@backstage/plugin-techdocs': patch
----
-
-Updated dependency `event-source-polyfill` to `1.0.26`.
diff --git a/.changeset/renovate-db1641c.md b/.changeset/renovate-db1641c.md
new file mode 100644
index 0000000000..8d34bd3eb1
--- /dev/null
+++ b/.changeset/renovate-db1641c.md
@@ -0,0 +1,7 @@
+---
+'@backstage/core-components': patch
+'@backstage/plugin-codescene': patch
+'@backstage/plugin-sonarqube': patch
+---
+
+Updated dependency `rc-progress` to `3.3.2`.
diff --git a/.changeset/short-ears-sit.md b/.changeset/short-ears-sit.md
new file mode 100644
index 0000000000..e75908a37d
--- /dev/null
+++ b/.changeset/short-ears-sit.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-graph': patch
+---
+
+Fix kind filter error in the dev app
diff --git a/.changeset/shy-rings-mix.md b/.changeset/shy-rings-mix.md
new file mode 100644
index 0000000000..de322fdf85
--- /dev/null
+++ b/.changeset/shy-rings-mix.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': patch
+---
+
+Fix SAML session schema to no longer require the (deprecated) id, to unbreak session data storage.
diff --git a/.changeset/sixty-bulldogs-hang.md b/.changeset/sixty-bulldogs-hang.md
new file mode 100644
index 0000000000..61367aad08
--- /dev/null
+++ b/.changeset/sixty-bulldogs-hang.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Add controls to Storybook stories
diff --git a/.changeset/spotty-papayas-shave.md b/.changeset/spotty-papayas-shave.md
new file mode 100644
index 0000000000..5a741b6b33
--- /dev/null
+++ b/.changeset/spotty-papayas-shave.md
@@ -0,0 +1,37 @@
+---
+'@backstage/create-app': patch
+---
+
+Integrates TechDocs add-ons with the app package so add-ons are configured when creating an app using the Backstage CLI. To apply these changes to an existing application do the following:
+
+1. Add the `@backstage/plugin-techdocs-react` and `@backstage/plugin-techdocs-module-addons-contrib` packages to your app's dependencies;
+2. And then register the `` Addon in your `packages/app/src/App.tsx` file, there where you define a route to ``:
+
+```diff
+import {
+ DefaultTechDocsHome,
+ TechDocsIndexPage,
+ TechDocsReaderPage,
+} from '@backstage/plugin-techdocs';
++ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
++ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+
+// ...
+
+const AppRoutes = () => {
+
+ // ... other plugin routes
+ }>
+
+
+ }
+ >
++
++
++
+
+ ;
+};
+```
diff --git a/.changeset/sweet-icons-sneeze.md b/.changeset/sweet-icons-sneeze.md
new file mode 100644
index 0000000000..b12424c7a8
--- /dev/null
+++ b/.changeset/sweet-icons-sneeze.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Fix `EntityTypeFilter` so generating available types is case insensitive
diff --git a/.changeset/tall-tables-smash.md b/.changeset/tall-tables-smash.md
new file mode 100644
index 0000000000..2bf277879e
--- /dev/null
+++ b/.changeset/tall-tables-smash.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-graphiql': patch
+'@backstage/plugin-api-docs': patch
+---
+
+Add the dependency `graphql-ws` to fix the latest release of `@graphiql/toolkit` that has a missing dependency
diff --git a/.changeset/techdocs-bob-law-blog.md b/.changeset/techdocs-bob-law-blog.md
new file mode 100644
index 0000000000..581ab19bec
--- /dev/null
+++ b/.changeset/techdocs-bob-law-blog.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-techdocs-react': major
+'@backstage/plugin-techdocs-addons-test-utils': major
+'@backstage/plugin-techdocs-module-addons-contrib': major
+---
+
+The TechDocs Addon framework is now generally available.
diff --git a/.changeset/techdocs-khaki-cheetahs-clap.md b/.changeset/techdocs-khaki-cheetahs-clap.md
new file mode 100644
index 0000000000..5aba436534
--- /dev/null
+++ b/.changeset/techdocs-khaki-cheetahs-clap.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-react': patch
+---
+
+Added overload signatures for `createTechDocsAddonExtension` to handle TechDocs addons without props.
diff --git a/.changeset/techdocs-light-treason.md b/.changeset/techdocs-light-treason.md
new file mode 100644
index 0000000000..52136a732d
--- /dev/null
+++ b/.changeset/techdocs-light-treason.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-node': patch
+---
+
+Updated the default version of the `@spotify/techdocs` container used when `techdocs.generator.runIn` is `docker` to `v1.0.3`.
diff --git a/.changeset/techdocs-seal-deal.md b/.changeset/techdocs-seal-deal.md
new file mode 100644
index 0000000000..865d1fc837
--- /dev/null
+++ b/.changeset/techdocs-seal-deal.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-backend': patch
+---
+
+Fixed a bug that could cause TechDocs index generation to hang and fail when an underlying TechDocs site's `search_index.json` was empty.
diff --git a/.changeset/techdocs-steve-holt.md b/.changeset/techdocs-steve-holt.md
new file mode 100644
index 0000000000..c456d48cf8
--- /dev/null
+++ b/.changeset/techdocs-steve-holt.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Hidden exports related to experimental TechDocs reader functionality have been removed and can no longer be imported. In the unlikely event you were using these exports, you can now take advantage of the officially supported and generally available TechDocs Addon framework instead.
diff --git a/.changeset/techdocs-stupid-radios-approve.md b/.changeset/techdocs-stupid-radios-approve.md
new file mode 100644
index 0000000000..813e895611
--- /dev/null
+++ b/.changeset/techdocs-stupid-radios-approve.md
@@ -0,0 +1,5 @@
+---
+'@techdocs/cli': patch
+---
+
+Update `techdocs-cli serve`'s `proxyEndpoint` to match the base URL of the embedded techdocs app.
diff --git a/.changeset/techdocs-weak-boats-work.md b/.changeset/techdocs-weak-boats-work.md
new file mode 100644
index 0000000000..ec4f089592
--- /dev/null
+++ b/.changeset/techdocs-weak-boats-work.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-module-addons-contrib': patch
+---
+
+Introducing the Expandable Navigation addon, which lets you expand and collapse the TechDocs main navigation and store your preference in local storage.
diff --git a/.changeset/techdocs-wee-britain.md b/.changeset/techdocs-wee-britain.md
new file mode 100644
index 0000000000..661ff76fc5
--- /dev/null
+++ b/.changeset/techdocs-wee-britain.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-addons-test-utils': patch
+---
+
+Fixed a type bug preventing `buildAddonsInTechDocs().withApis()` from being called with multiple partial API implementations.
diff --git a/.changeset/tough-queens-hope.md b/.changeset/tough-queens-hope.md
new file mode 100644
index 0000000000..6816893611
--- /dev/null
+++ b/.changeset/tough-queens-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Added a chevron Indicator when the sidebar is collapsed and has a sub-menu
diff --git a/.changeset/unlucky-bobcats-fold.md b/.changeset/unlucky-bobcats-fold.md
new file mode 100644
index 0000000000..6dd820db8d
--- /dev/null
+++ b/.changeset/unlucky-bobcats-fold.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': minor
+---
+
+Removed the explicit `disableRefresh` option from `OAuthAdapter`. Refresh can still be disabled for a provider by not implementing the `refresh` method.
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index ff351a5e0f..a8bc3448a5 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -3,7 +3,7 @@
extends: ['config:base', ':disableDependencyDashboard', ':gitSignOff'],
rangeStrategy: 'update-lockfile',
// @elastic/elasticsearch is ignored due to licensing issues. See #10992
- ignoreDeps: ['@elastic/elasticsearch'],
+ ignoreDeps: ['@elastic/elasticsearch', 'event-source-polyfill'],
packageRules: [
{
matchSourceUrlPrefixes: ['https://github.com/spotify/web-scripts'],
diff --git a/.github/workflows/verify_dco.yaml b/.github/workflows/verify_dco.yaml
index 71bd322eca..2094327d23 100644
--- a/.github/workflows/verify_dco.yaml
+++ b/.github/workflows/verify_dco.yaml
@@ -54,7 +54,7 @@ jobs:
console.log(`creating comment on PR #${pull.number}`);
const body = `
Thanks for the contribution!
- All commits need to be DCO signed before merging. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info.
+ All commits need to be DCO signed before they are reviewed. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info.
`;
await github.rest.issues.createComment({
repo,
diff --git a/ADOPTERS.md b/ADOPTERS.md
index b163a5c612..3915aee949 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -125,3 +125,5 @@ _If you're using Backstage in your organization, please try to add your company
| [Back Market](https://www.backmarket.com) | [Sami Farhat](https://github.com/skfarhat) | Internal Developer Portal featuring catalog, tech-radar, ownership management, component creation (scaffolder) and centralized infrastructure management -- probably more to come. |
| [Avalia Systems](https://avalia.io) | [Olivier Liechti](https://github.com/wasadigi), [Fabio Velloso](https://github.com/fabiovelloso) | Innersource, software analytics, knowledge base for 360 software assessments, collaborative applications, hub for tracking and sharing IP assets. |
| [Albert Heijn](https://ah.technology) | [Joost Hofman](https://github.com/joosthofman), [Reindrich Geerman](https://github.com/reinst) | Single point of entry for all our engineers (Developer portal), Tech radar, catalog, templates (paved roads) and tech documentation. |
+| [Wise, formerly TransferWise](https://wise.com) | [Andrew Beveridge](https://github.com/beveradb) | It's early days for us, we're trying to start small with catalog, tech docs and unified search. Future ambitious vision includes scaffolder for one-click component addition, building out integrations with CI/CD tooling, kubernetes clusters, monitoring/alerting tooling etc. and aiming for a frictionless "golden path" for engineers! 🚀 |
+
diff --git a/cypress/src/integration/plugins/techdocs.spec.ts b/cypress/src/integration/plugins/techdocs.spec.ts
index d6049ea53d..1e69ba46ad 100644
--- a/cypress/src/integration/plugins/techdocs.spec.ts
+++ b/cypress/src/integration/plugins/techdocs.spec.ts
@@ -38,6 +38,7 @@ describe('TechDocs', () => {
it('should navigate to the TechDocs home page from the URL', () => {
cy.visit('/docs');
+
cy.wait(500);
cy.contains('Documentation');
@@ -45,15 +46,12 @@ describe('TechDocs', () => {
it('should navigate to a specific TechDocs entity from the "Overview" tab', () => {
cy.visit('/docs');
- cy.contains('techdocs-e2e-fixture')
- .parents()
- .eq(2)
- .contains('Read Docs')
- .click();
+
+ cy.contains('techdocs-e2e-fixture').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
- '/docs/default/Component/techdocs-e2e-fixture',
+ '/docs/default/component/techdocs-e2e-fixture',
);
});
});
@@ -84,7 +82,6 @@ describe('TechDocs', () => {
cy.visit(
'/docs/default/Component/techdocs-e2e-fixture/sub-page-two#section-23',
);
- cy.waitSectionTwoPage();
// This is used to test the post-render behavior of the techdocs Reader
cy.wait(500);
@@ -101,10 +98,43 @@ describe('TechDocs', () => {
});
});
+ describe('Rendering TechDocs Addons', () => {
+ it('should render a content addon', () => {
+ cy.visit('/docs/default/Component/techdocs-e2e-fixture');
+
+ cy.contains('e2e Fixture Documentation');
+
+ // highlight a snippet of text
+ cy.getTechDocsShadowRoot()
+ .find('article > p')
+ .then($el => {
+ const el = $el[0];
+ const document = el.ownerDocument;
+ const range = document.createRange();
+ range.selectNodeContents(el);
+ document?.getSelection()?.removeAllRanges();
+ document?.getSelection()?.addRange(range);
+ });
+
+ cy.document().trigger('selectionchange');
+
+ // wait for new issue default debounce time
+ cy.wait(600);
+
+ // assert that the new issue button has a right url
+ cy.getTechDocsShadowRoot()
+ .contains('Open new Github issue')
+ .should(
+ 'have.attr',
+ 'href',
+ 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E',
+ );
+ });
+ });
+
describe('Navigating within TechDocs', () => {
it('should navigate to a specific TechDocs page via the navigation bar', () => {
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
- cy.waitHomePage();
cy.getTechDocsShadowRoot().within(() => {
cy.getTechDocsNavigation()
@@ -118,14 +148,18 @@ describe('TechDocs', () => {
describe('Navigating within a TechDocs page', () => {
beforeEach(() => {
cy.visit('/docs/default/Component/techdocs-e2e-fixture/sub-page-two');
- cy.waitSectionTwoPage();
});
+
it('should navigate to a specific fragment within the page via the table of contents - Level 1', () => {
return cy.getTechDocsShadowRoot().within(() => {
// Section 3
- cy.getTechDocsTableOfContents().within(() => {
- cy.get('> div > div > nav > ul > li:nth-child(3) > a').click();
- });
+ cy.getTechDocsTableOfContents()
+ .find('a')
+ .contains('Section 2.3')
+ .click();
+
+ // wait scroll timeout
+ cy.wait(500);
cy.isInViewport('#section-23');
});
@@ -136,20 +170,20 @@ describe('TechDocs', () => {
cy.isNotInViewport('#sub-section-222');
// Section 2.2
cy.getTechDocsTableOfContents()
- .find(
- '> div > div > nav > ul > li:nth-child(2) > nav > ul > li:nth-child(2) > a',
- )
+ .find('a')
+ .contains('Section 2.2.2')
.click();
+ // wait scroll timeout
+ cy.wait(500);
+
cy.isInViewport('#sub-section-222');
});
});
it('should navigate to a specific TechDocs page fragment from a link', () => {
return cy.getTechDocsShadowRoot().within(() => {
- cy.get('.md-content > article')
- .contains('Link to Section 1.1')
- .click();
+ cy.get('article').contains('Link to Section 1.1').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
@@ -162,7 +196,7 @@ describe('TechDocs', () => {
it('should navigate to the next page within a TechDocs entity', () => {
return cy.getTechDocsShadowRoot().within(() => {
- cy.get('.md-footer-nav__link--next').click();
+ cy.get('footer a').contains('Sub-page 3').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
@@ -174,7 +208,7 @@ describe('TechDocs', () => {
it('should navigate to the previous page within a TechDocs entity', () => {
return cy.getTechDocsShadowRoot().within(() => {
- cy.get('.md-footer-nav__link--prev').click();
+ cy.get('footer a').contains('Sub-page 1').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
diff --git a/cypress/src/support/commands.ts b/cypress/src/support/commands.ts
index 666dc157d7..9973afb6a7 100644
--- a/cypress/src/support/commands.ts
+++ b/cypress/src/support/commands.ts
@@ -25,7 +25,7 @@ Cypress.Commands.add('loginAsGuest', () => {
});
Cypress.Commands.add('getTechDocsShadowRoot', () => {
- cy.get('[data-testid="techdocs-content-shadowroot"]').shadow();
+ cy.get('[data-testid="techdocs-native-shadowroot"]').shadow();
});
Cypress.Commands.add('isNotInViewport', element => {
@@ -57,11 +57,11 @@ Cypress.Commands.add('isInViewport', element => {
});
Cypress.Commands.add('getTechDocsTableOfContents', () => {
- cy.get('[data-md-component="toc"]');
+ cy.get('[data-md-type="toc"]');
});
Cypress.Commands.add('getTechDocsNavigation', () => {
- cy.get('[data-md-component="navigation"]');
+ cy.get('[data-md-type="navigation"]');
});
Cypress.Commands.add('mockSockJSNode', () => {
diff --git a/docs/assets/techdocs/report-issue-addon.png b/docs/assets/techdocs/report-issue-addon.png
new file mode 100644
index 0000000000..4c61935f2f
Binary files /dev/null and b/docs/assets/techdocs/report-issue-addon.png differ
diff --git a/docs/assets/techdocs/report-issue-template.png b/docs/assets/techdocs/report-issue-template.png
new file mode 100644
index 0000000000..35dca05e1f
Binary files /dev/null and b/docs/assets/techdocs/report-issue-template.png differ
diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md
index 282088fef9..bd2bcd1694 100644
--- a/docs/auth/add-auth-provider.md
+++ b/docs/auth/add-auth-provider.md
@@ -153,7 +153,6 @@ export const createOktaProvider: AuthProviderFactory = ({
// Wrap the OAuthProviderHandlers with OAuthProvider, which implements AuthProviderRouteHandlers
return OAuthProvider.fromConfig(globalConfig, provider, {
- disableRefresh: false,
providerId,
tokenIssuer,
});
diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md
index e8af56d3fc..5305e3bc68 100644
--- a/docs/features/techdocs/addons.md
+++ b/docs/features/techdocs/addons.md
@@ -111,10 +111,11 @@ page header, TechDocs Addons whose location is `Header` will not be rendered.
Addons can, in principle, be provided by any plugin! To make it easier to
discover available Addons, we've compiled a list of them here:
-| Addon | Package/Plugin | Description |
-| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
-| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
+| Addon | Package/Plugin | Description |
+| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
+| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
+| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
Got an Addon to contribute? Feel free to add a row above!
diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md
index 9a7dccd5a6..52c389975e 100644
--- a/docs/features/techdocs/cli.md
+++ b/docs/features/techdocs/cli.md
@@ -120,7 +120,7 @@ Generate TechDocs documentation site using MkDocs.
Options:
--source-dir Source directory containing mkdocs.yml and docs/ directory. (default: ".")
--output-dir Output directory containing generated TechDocs site. (default: "./site/")
- --docker-image The mkdocs docker container to use (default: "spotify/techdocs:v1.0.2")
+ --docker-image The mkdocs docker container to use (default: "spotify/techdocs:v1.0.3")
--no-pull Do not pull the latest docker image
--no-docker Do not use Docker, use MkDocs executable and plugins in current user environment.
--techdocs-ref The repository hosting documentation source files e.g.
diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md
index 1eeebae903..f8bf98c0e0 100644
--- a/docs/features/techdocs/getting-started.md
+++ b/docs/features/techdocs/getting-started.md
@@ -54,8 +54,48 @@ const AppRoutes = () => {
};
```
-That's it! But now, we need the TechDocs Backend plugin for the frontend to
-work.
+It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework!
+
+With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed:
+
+```diff
+import {
+ DefaultTechDocsHome,
+ TechDocsIndexPage,
+ TechDocsReaderPage,
+} from '@backstage/plugin-techdocs';
++ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
++ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+
+// ...
+
+const AppRoutes = () => {
+
+ // ... other plugin routes
+ }>
+
+
+ }
+ >
++
++
++
+
+ ;
+};
+```
+
+I know, you're curious to see how it looks, aren't you? See the image below:
+
+
+
+By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using:
+
+
+
+That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
## Adding TechDocs Backend plugin
diff --git a/docs/releases/v1.2.0-next.3-changelog.md b/docs/releases/v1.2.0-next.3-changelog.md
new file mode 100644
index 0000000000..4efce476d4
--- /dev/null
+++ b/docs/releases/v1.2.0-next.3-changelog.md
@@ -0,0 +1,73 @@
+# Release v1.2.0-next.3
+
+## @backstage/core-components@0.9.4-next.2
+
+### Patch Changes
+
+- 52c02ac02b: Don't set the background color on an Avatar component that has a picture.
+- 3603014e0e: Add ARIA landmark( ), & label and a heading to OAuthRequestDialog. Removed nested interactive control (button).
+- 2025d7c123: Properly highlight `SidebarSubmenuItem` dropdown items on hover, use ellipsis styling on long labels in `SidebarSubmenu`, allow `icon` and `to` properties to be optional on `SidebarSubmenuItem`, and fix `SidebarPage` padding to be responsive to pinned state
+
+## @backstage/plugin-home@0.4.21-next.3
+
+### Patch Changes
+
+- 69093c5f91: Display entity titles in `StarredEntities` home page card (if defined) and don't show entities which no longer exist
+- Updated dependencies
+ - @backstage/core-components@0.9.4-next.2
+
+## @backstage/plugin-kubernetes@0.6.5-next.3
+
+### Patch Changes
+
+- 447e060872: Add support for 'oidc' as authProvider for kubernetes authentication
+ and adds optional 'oidcTokenProvider' config value. This will allow
+ users to authenticate to kubernetes cluster using id tokens obtained
+ from the configured auth provider in their backstage instance.
+- Updated dependencies
+ - @backstage/plugin-kubernetes-common@0.2.10-next.1
+ - @backstage/core-components@0.9.4-next.2
+
+## @backstage/plugin-kubernetes-backend@0.5.1-next.2
+
+### Patch Changes
+
+- 447e060872: Add support for 'oidc' as authProvider for kubernetes authentication
+ and adds optional 'oidcTokenProvider' config value. This will allow
+ users to authenticate to kubernetes cluster using id tokens obtained
+ from the configured auth provider in their backstage instance.
+- Updated dependencies
+ - @backstage/plugin-kubernetes-common@0.2.10-next.1
+
+## @backstage/plugin-kubernetes-common@0.2.10-next.1
+
+### Patch Changes
+
+- 447e060872: Add support for 'oidc' as authProvider for kubernetes authentication
+ and adds optional 'oidcTokenProvider' config value. This will allow
+ users to authenticate to kubernetes cluster using id tokens obtained
+ from the configured auth provider in their backstage instance.
+
+## @backstage/plugin-org@0.5.5-next.3
+
+### Patch Changes
+
+- 2025d7c123: Include namespace in `MyGroupSidebarItem` if not default and remove root item routing if there are multiple groups
+- Updated dependencies
+ - @backstage/core-components@0.9.4-next.2
+
+## @backstage/plugin-scaffolder@1.2.0-next.3
+
+### Patch Changes
+
+- cc8ddd0979: revert dependency `event-source-polyfill` to `1.0.25`
+- Updated dependencies
+ - @backstage/core-components@0.9.4-next.2
+
+## @backstage/plugin-techdocs@1.1.1-next.3
+
+### Patch Changes
+
+- cc8ddd0979: revert dependency `event-source-polyfill` to `1.0.25`
+- Updated dependencies
+ - @backstage/core-components@0.9.4-next.2
diff --git a/microsite/data/on-demand/20220420-1.yaml b/microsite/data/on-demand/20220420-1.yaml
index 6089ac1cc9..96560ec436 100644
--- a/microsite/data/on-demand/20220420-1.yaml
+++ b/microsite/data/on-demand/20220420-1.yaml
@@ -1,7 +1,7 @@
---
title: Adopters Community Sessions
date: April 20, 2022
-category: Upcoming
+category: Meetup
description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc.
youtubeUrl: https://youtu.be/mFi_X58igzk
youtubeImgUrl: https://backstage.io/img/b-sessions.png
diff --git a/microsite/data/on-demand/20220427-1.yaml b/microsite/data/on-demand/20220427-1.yaml
index 451ee3bc67..80ec8eae00 100644
--- a/microsite/data/on-demand/20220427-1.yaml
+++ b/microsite/data/on-demand/20220427-1.yaml
@@ -1,7 +1,7 @@
---
title: Contributor Community Sessions
date: April 27, 2022
-category: Upcoming
+category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/evf_LV0KzIk
youtubeImgUrl: https://backstage.io/img/b-sessions.png
diff --git a/microsite/data/on-demand/20220518-01.yaml b/microsite/data/on-demand/20220518-01.yaml
new file mode 100644
index 0000000000..e98c79005a
--- /dev/null
+++ b/microsite/data/on-demand/20220518-01.yaml
@@ -0,0 +1,9 @@
+---
+title: Adopters Community Sessions
+date: May 18, 2022
+category: Upcoming
+description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc.
+youtubeUrl: https://youtu.be/dEd1fl3wRv
+youtubeImgUrl: https://backstage.io/img/b-sessions.png
+rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
+eventUrl: https://github.com/backstage/community/issues/46
diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml
new file mode 100644
index 0000000000..03d5d66947
--- /dev/null
+++ b/microsite/data/plugins/kpt-config-as-data.yaml
@@ -0,0 +1,9 @@
+---
+title: Configuration as Data
+author: kpt
+authorUrl: https://kpt.dev
+category: Configuration
+description: Configuration GUI over GitOps using kpt, with WYSIWYG editing, review and approval, versioning and undo, and package cloning and upgrades.
+documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tree/main/plugins/cad
+iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true
+npmPackageName: '@kpt/backstage-plugin-cad'
diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js
index 374b690d89..9f8a0e24b0 100644
--- a/microsite/pages/en/live.js
+++ b/microsite/pages/en/live.js
@@ -34,7 +34,7 @@ const Background = props => {
diff --git a/package.json b/package.json
index d415477294..c215ef69a4 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
"@types/react": "^17",
"@types/react-dom": "^17"
},
- "version": "1.2.0-next.2",
+ "version": "1.2.0-next.3",
"dependencies": {
"@manypkg/get-packages": "^1.1.3",
"@microsoft/api-documenter": "^7.17.11",
@@ -68,7 +68,7 @@
"concurrently": "^7.0.0",
"eslint-plugin-notice": "^0.9.10",
"fs-extra": "10.1.0",
- "husky": "^7.0.4",
+ "husky": "^8.0.0",
"lerna": "^4.0.0",
"lint-staged": "^12.2.0",
"minimist": "^1.2.5",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 089900fb77..35c61f2f16 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -71,6 +71,7 @@ import {
} from '@backstage/plugin-techdocs';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import {
+ ExpandableNavigation,
ReportIssue,
TextSize,
} from '@backstage/plugin-techdocs-module-addons-contrib';
@@ -185,6 +186,7 @@ const routes = (
>
{techDocsPage}
+
diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md
index 13c4ed62ce..6059e68792 100644
--- a/packages/backend-common/api-report.md
+++ b/packages/backend-common/api-report.md
@@ -617,18 +617,20 @@ export class ServerTokenManager implements TokenManager {
// (undocumented)
static fromConfig(
config: Config,
- options: {
- logger: Logger;
- },
+ options: ServerTokenManagerOptions,
): ServerTokenManager;
// (undocumented)
getToken(): Promise<{
token: string;
}>;
- // (undocumented)
static noop(): TokenManager;
}
+// @public
+export interface ServerTokenManagerOptions {
+ logger: Logger;
+}
+
// @public
export type ServiceBuilder = {
loadConfig(config: Config): ServiceBuilder;
@@ -687,10 +689,8 @@ export interface StatusCheckHandlerOptions {
// @public
export interface TokenManager {
- // (undocumented)
- authenticate: (token: string) => Promise;
- // (undocumented)
- getToken: () => Promise<{
+ authenticate(token: string): Promise;
+ getToken(): Promise<{
token: string;
}>;
}
diff --git a/packages/backend-common/src/tokens/ServerTokenManager.test.ts b/packages/backend-common/src/tokens/ServerTokenManager.test.ts
index 0ff7bfb316..6a72b99908 100644
--- a/packages/backend-common/src/tokens/ServerTokenManager.test.ts
+++ b/packages/backend-common/src/tokens/ServerTokenManager.test.ts
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { getVoidLogger } from '../logging/voidLogger';
+
import { ConfigReader } from '@backstage/config';
-import { ServerTokenManager } from './ServerTokenManager';
-import { Logger } from 'winston';
import * as jose from 'jose';
+import { Logger } from 'winston';
+import { getVoidLogger } from '../logging/voidLogger';
+import { ServerTokenManager } from './ServerTokenManager';
import { TokenManager } from './types';
const emptyConfig = new ConfigReader({});
@@ -35,6 +36,7 @@ describe('ServerTokenManager', () => {
afterEach(() => {
process.env = env;
+ jest.useRealTimers();
});
describe('getToken', () => {
@@ -166,6 +168,42 @@ describe('ServerTokenManager', () => {
/invalid server token/i,
);
});
+
+ it('should throw for expired tokens, and re-issue new ones', async () => {
+ jest.useFakeTimers();
+ const warn = jest.spyOn(logger, 'warn');
+
+ const tokenManager = ServerTokenManager.fromConfig(configWithSecret, {
+ logger,
+ });
+
+ const { token: token1 } = await tokenManager.getToken();
+ await expect(tokenManager.authenticate(token1)).resolves.not.toThrow();
+ expect(warn.mock.calls.length).toBe(0);
+
+ // Right before the reissue timeout, it still returns the same token
+ jest.advanceTimersByTime(9 * 60 * 1000);
+ const { token: token1Again } = await tokenManager.getToken();
+ expect(token1).toEqual(token1Again);
+ await expect(tokenManager.authenticate(token1)).resolves.not.toThrow();
+ expect(warn.mock.calls.length).toBe(0);
+
+ // Right after the reissue timeout, the old ones are still valid but returning a new token
+ jest.advanceTimersByTime(2 * 60 * 1000);
+ const { token: token2 } = await tokenManager.getToken();
+ expect(token1).not.toEqual(token2);
+ await expect(tokenManager.authenticate(token1)).resolves.not.toThrow();
+ await expect(tokenManager.authenticate(token2)).resolves.not.toThrow();
+ expect(warn.mock.calls.length).toBe(0);
+
+ // After expiry of the first one, it gets warnings but the newest one is still valid
+ jest.advanceTimersByTime(52 * 60 * 1000);
+ await expect(tokenManager.authenticate(token1)).resolves.not.toThrow();
+ await expect(tokenManager.authenticate(token2)).resolves.not.toThrow();
+ expect(warn.mock.calls[0][0]).toMatchInlineSnapshot(
+ '"#### DEPRECATION WARNING: #### Server-to-server token had an expired exp claim, support for this has been deprecated and will result in errors in a future release"',
+ );
+ });
});
describe('fromConfig', () => {
@@ -242,7 +280,7 @@ describe('ServerTokenManager', () => {
});
});
- describe('ServerTokenManager.noop', () => {
+ describe('noop', () => {
let noopTokenManager: TokenManager;
beforeEach(() => {
diff --git a/packages/backend-common/src/tokens/ServerTokenManager.ts b/packages/backend-common/src/tokens/ServerTokenManager.ts
index 484b99ca4d..329c2f164c 100644
--- a/packages/backend-common/src/tokens/ServerTokenManager.ts
+++ b/packages/backend-common/src/tokens/ServerTokenManager.ts
@@ -14,12 +14,22 @@
* limitations under the License.
*/
-import { base64url, generateSecret, SignJWT, jwtVerify, exportJWK } from 'jose';
import { Config } from '@backstage/config';
-import { AuthenticationError } from '@backstage/errors';
-import { TokenManager } from './types';
+import { AuthenticationError, NotAllowedError } from '@backstage/errors';
+import { base64url, exportJWK, generateSecret, jwtVerify, SignJWT } from 'jose';
+import { DateTime, Duration } from 'luxon';
import { Logger } from 'winston';
+import { TokenManager } from './types';
+const TOKEN_ALG = 'HS256';
+const TOKEN_SUB = 'backstage-server';
+const TOKEN_EXPIRY_AFTER = Duration.fromObject({ hours: 1 });
+const TOKEN_REISSUE_AFTER = Duration.fromObject({ minutes: 10 });
+
+/**
+ * A token manager that issues static dummy tokens and never fails
+ * authentication. This can be useful for testing.
+ */
class NoopTokenManager implements TokenManager {
public readonly isInsecureServerTokenManager: boolean = true;
@@ -30,6 +40,18 @@ class NoopTokenManager implements TokenManager {
async authenticate() {}
}
+/**
+ * Options for {@link ServerTokenManager}.
+ *
+ * @public
+ */
+export interface ServerTokenManagerOptions {
+ /**
+ * The logger to use.
+ */
+ logger: Logger;
+}
+
/**
* Creates and validates tokens for use during backend-to-backend
* authentication.
@@ -37,44 +59,51 @@ class NoopTokenManager implements TokenManager {
* @public
*/
export class ServerTokenManager implements TokenManager {
- private verificationKeys: Uint8Array[];
+ private readonly options: ServerTokenManagerOptions;
+ private readonly verificationKeys: Uint8Array[];
private signingKey: Uint8Array;
- private privateKeyPromise?: Promise;
- private logger: Logger;
+ private privateKeyPromise: Promise | undefined;
+ private currentTokenPromise: Promise<{ token: string }> | undefined;
+ private warnedForMissingExpClaim = false;
+ private warnedForExpiredExpClaim = false;
+ /**
+ * Creates a token manager that issues static dummy tokens and never fails
+ * authentication. This can be useful for testing.
+ */
static noop(): TokenManager {
return new NoopTokenManager();
}
- static fromConfig(config: Config, options: { logger: Logger }) {
- const { logger } = options;
-
+ static fromConfig(config: Config, options: ServerTokenManagerOptions) {
const keys = config.getOptionalConfigArray('backend.auth.keys');
if (keys?.length) {
return new ServerTokenManager(
keys.map(key => key.getString('secret')),
- logger,
+ options,
);
}
+
if (process.env.NODE_ENV !== 'development') {
throw new Error(
'You must configure at least one key in backend.auth.keys for production.',
);
}
+
// For development, if a secret has not been configured, we auto generate a secret instead of throwing.
- logger.warn(
+ options.logger.warn(
'Generated a secret for backend-to-backend authentication: DEVELOPMENT USE ONLY.',
);
- return new ServerTokenManager([], logger);
+ return new ServerTokenManager([], options);
}
- private constructor(secrets: string[], logger: Logger) {
+ private constructor(secrets: string[], options: ServerTokenManagerOptions) {
if (!secrets.length && process.env.NODE_ENV !== 'development') {
throw new Error(
'No secrets provided when constructing ServerTokenManager',
);
}
- this.logger = logger;
+ this.options = options;
this.verificationKeys = secrets.map(s => base64url.decode(s));
this.signingKey = this.verificationKeys[0];
}
@@ -86,11 +115,13 @@ export class ServerTokenManager implements TokenManager {
'Key generation is not supported outside of the dev environment',
);
}
+
if (this.privateKeyPromise) {
return this.privateKeyPromise;
}
+
const promise = (async () => {
- const secret = await generateSecret('HS256');
+ const secret = await generateSecret(TOKEN_ALG);
const jwk = await exportJWK(secret);
this.verificationKeys.push(base64url.decode(jwk.k ?? ''));
this.signingKey = this.verificationKeys[0];
@@ -98,13 +129,15 @@ export class ServerTokenManager implements TokenManager {
})();
try {
- // If we fail to generate a new key, we need to clear the state so that
- // the next caller will try to generate another key.
+ this.privateKeyPromise = promise;
await promise;
} catch (error) {
- this.logger.error(`Failed to generate new key, ${error}`);
+ // If we fail to generate a new key, we need to clear the state so that
+ // the next caller will try to generate another key.
+ this.options.logger.error(`Failed to generate new key, ${error}`);
delete this.privateKeyPromise;
}
+
return promise;
}
@@ -112,26 +145,83 @@ export class ServerTokenManager implements TokenManager {
if (!this.verificationKeys.length) {
await this.generateKeys();
}
- const sub = 'backstage-server';
- const jwt = await new SignJWT({ alg: 'HS256' })
- .setProtectedHeader({ alg: 'HS256', sub: sub })
- .setSubject('backstage-server')
- .sign(this.signingKey);
- return { token: jwt };
+
+ if (this.currentTokenPromise) {
+ return this.currentTokenPromise;
+ }
+
+ const result = Promise.resolve().then(async () => {
+ const jwt = await new SignJWT({})
+ .setProtectedHeader({ alg: TOKEN_ALG })
+ .setSubject(TOKEN_SUB)
+ .setExpirationTime(
+ DateTime.now().plus(TOKEN_EXPIRY_AFTER).toUnixInteger(),
+ )
+ .sign(this.signingKey);
+ return { token: jwt };
+ });
+
+ this.currentTokenPromise = result;
+
+ result
+ .then(() => {
+ setTimeout(() => {
+ this.currentTokenPromise = undefined;
+ }, TOKEN_REISSUE_AFTER.toMillis());
+ })
+ .catch(() => {
+ this.currentTokenPromise = undefined;
+ });
+
+ return result;
}
async authenticate(token: string): Promise {
let verifyError = undefined;
+
for (const key of this.verificationKeys) {
try {
- await jwtVerify(token, key);
- // If the verify succeeded, return
+ const {
+ protectedHeader: { alg },
+ payload: { sub, exp },
+ } = await jwtVerify(token, key, {
+ // TODO(freben): Holding on to tokens and reusing them is deprecated; remove this tolerance in a future release
+ clockTolerance: 3e9,
+ });
+
+ if (alg !== TOKEN_ALG) {
+ throw new NotAllowedError(`Illegal alg "${alg}"`);
+ }
+
+ if (sub !== TOKEN_SUB) {
+ throw new NotAllowedError(`Illegal sub "${sub}"`);
+ }
+
+ // TODO(freben): Passing in tokens without an exp is deprecated; change this warning to an error in a future release
+ if (typeof exp !== 'number') {
+ if (!this.warnedForMissingExpClaim) {
+ this.warnedForMissingExpClaim = true;
+ this.options.logger.warn(
+ `#### DEPRECATION WARNING: #### Server-to-server token had no exp claim, support for this has been deprecated and will result in errors in a future release`,
+ );
+ }
+ }
+ // TODO(freben): Holding on to tokens and reusing them is deprecated; remove this tolerance in a future release
+ else if (exp * 1000 < Date.now()) {
+ if (!this.warnedForExpiredExpClaim) {
+ this.warnedForExpiredExpClaim = true;
+ this.options.logger.warn(
+ `#### DEPRECATION WARNING: #### Server-to-server token had an expired exp claim, support for this has been deprecated and will result in errors in a future release`,
+ );
+ }
+ }
return;
} catch (e) {
// Catch the verify exception and continue
verifyError = e;
}
}
+
throw new AuthenticationError(`Invalid server token: ${verifyError}`);
}
}
diff --git a/packages/backend-common/src/tokens/index.ts b/packages/backend-common/src/tokens/index.ts
index 43ff12e597..fe1b56df80 100644
--- a/packages/backend-common/src/tokens/index.ts
+++ b/packages/backend-common/src/tokens/index.ts
@@ -15,4 +15,5 @@
*/
export { ServerTokenManager } from './ServerTokenManager';
+export type { ServerTokenManagerOptions } from './ServerTokenManager';
export type { TokenManager } from './types';
diff --git a/packages/backend-common/src/tokens/types.ts b/packages/backend-common/src/tokens/types.ts
index 1fea018db9..2fa771b7e2 100644
--- a/packages/backend-common/src/tokens/types.ts
+++ b/packages/backend-common/src/tokens/types.ts
@@ -20,6 +20,20 @@
* @public
*/
export interface TokenManager {
- getToken: () => Promise<{ token: string }>;
- authenticate: (token: string) => Promise;
+ /**
+ * Fetches a valid token.
+ *
+ * @remarks
+ *
+ * Tokens are valid for roughly one hour; the actual deadline is set in the
+ * payload `exp` claim. Never hold on to tokens for reuse; always ask for a
+ * new one for each outgoing request. This ensures that you always get a
+ * valid, fresh one.
+ */
+ getToken(): Promise<{ token: string }>;
+
+ /**
+ * Validates a given token.
+ */
+ authenticate(token: string): Promise;
}
diff --git a/packages/catalog-model/examples/all.yaml b/packages/catalog-model/examples/all.yaml
index 1101a60ef9..c8e073345c 100644
--- a/packages/catalog-model/examples/all.yaml
+++ b/packages/catalog-model/examples/all.yaml
@@ -1,7 +1,7 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
- name: example-apis
+ name: example-all
description: A collection of all Backstage example entities, except users, groups, and templates
spec:
targets:
diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/types.ts b/packages/core-app-api/src/apis/implementations/auth/saml/types.ts
index 55b278b429..e5e32e3457 100644
--- a/packages/core-app-api/src/apis/implementations/auth/saml/types.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/saml/types.ts
@@ -34,7 +34,6 @@ export const samlSessionSchema: z.ZodSchema = z.object({
picture: z.string().optional(),
}),
backstageIdentity: z.object({
- id: z.string(),
token: z.string(),
identity: z.object({
type: z.literal('user'),
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 70d5d6209b..05efac4a62 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/core-components
+## 0.9.4-next.2
+
+### Patch Changes
+
+- 52c02ac02b: Don't set the background color on an Avatar component that has a picture.
+- 3603014e0e: Add ARIA landmark( ), & label and a heading to OAuthRequestDialog. Removed nested interactive control (button).
+- 2025d7c123: Properly highlight `SidebarSubmenuItem` dropdown items on hover, use ellipsis styling on long labels in `SidebarSubmenu`, allow `icon` and `to` properties to be optional on `SidebarSubmenuItem`, and fix `SidebarPage` padding to be responsive to pinned state
+
## 0.9.4-next.1
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index ff2b3553c8..e2f05aff2d 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
- "version": "0.9.4-next.1",
+ "version": "0.9.4-next.2",
"private": false,
"publishConfig": {
"access": "public",
@@ -55,7 +55,7 @@
"pluralize": "^8.0.0",
"prop-types": "^15.7.2",
"qs": "^6.9.4",
- "rc-progress": "3.2.4",
+ "rc-progress": "3.3.2",
"react-helmet": "6.1.0",
"react-hook-form": "^7.12.2",
"react-markdown": "^8.0.0",
diff --git a/packages/core-components/src/components/Avatar/Avatar.test.tsx b/packages/core-components/src/components/Avatar/Avatar.test.tsx
index 1c08d57679..d7bbf712fe 100644
--- a/packages/core-components/src/components/Avatar/Avatar.test.tsx
+++ b/packages/core-components/src/components/Avatar/Avatar.test.tsx
@@ -17,6 +17,7 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Avatar } from './Avatar';
+import { stringToColor } from './utils';
describe('', () => {
it('renders without exploding', async () => {
@@ -24,4 +25,24 @@ describe('', () => {
expect(getByText('JD')).toBeInTheDocument();
});
+
+ it('generates a background color', async () => {
+ const bgcolor = stringToColor('John Doe');
+ const { getByText } = render();
+ expect(getByText('JD')).toHaveStyle(`background-color: ${bgcolor}`);
+ });
+
+ it('does not generate a background color when a picture is given', async () => {
+ const bgcolor = stringToColor('John Doe');
+ const { getByAltText } = render(
+ ,
+ );
+
+ expect(getByAltText('John Doe')).not.toHaveStyle(
+ `background-color: ${bgcolor}`,
+ );
+ });
});
diff --git a/packages/core-components/src/components/Avatar/Avatar.tsx b/packages/core-components/src/components/Avatar/Avatar.tsx
index 304ee574d2..af8dabf554 100644
--- a/packages/core-components/src/components/Avatar/Avatar.tsx
+++ b/packages/core-components/src/components/Avatar/Avatar.tsx
@@ -67,15 +67,23 @@ export interface AvatarProps {
export function Avatar(props: AvatarProps) {
const { displayName, picture, customStyles } = props;
const classes = useStyles();
+ let styles = { ...customStyles };
+ // We only calculate the background color if there's not an avatar
+ // picture. If there is a picture, it might have a transparent
+ // background and we don't know whether the calculated background
+ // color will clash.
+ if (!picture) {
+ styles = {
+ backgroundColor: stringToColor(displayName || ''),
+ ...customStyles,
+ };
+ }
return (
{displayName && extractInitials(displayName)}
diff --git a/packages/core-components/src/components/Chip/Chip.stories.tsx b/packages/core-components/src/components/Chip/Chip.stories.tsx
index f5cdfd3f16..5e00779209 100644
--- a/packages/core-components/src/components/Chip/Chip.stories.tsx
+++ b/packages/core-components/src/components/Chip/Chip.stories.tsx
@@ -15,29 +15,51 @@
*/
import React from 'react';
-import Chip from '@material-ui/core/Chip';
+import AddIcon from '@material-ui/icons/Add';
+import WarningIcon from '@material-ui/icons/Warning';
+import EditIcon from '@material-ui/icons/Edit';
+import Chip, { ChipProps } from '@material-ui/core/Chip';
+
+const icons = {
+ AddIcon: ,
+ WarningIcon: ,
+ EditIcon: ,
+ None: null,
+};
+
+const defaultArgs = {
+ label: 'Label',
+ size: 'medium',
+ variant: 'default',
+ icon: 'None',
+};
export default {
title: 'Data Display/Chip',
component: Chip,
+ argTypes: {
+ size: {
+ options: ['small', 'medium'],
+ control: { type: 'select' },
+ },
+ variant: {
+ options: ['default', 'outlined'],
+ control: { type: 'select' },
+ },
+ icon: {
+ options: Object.keys(icons),
+ mapping: icons,
+ control: {
+ type: 'select',
+ },
+ },
+ },
};
-export const Default = () => ;
+export const Default = (args: ChipProps) => ;
+Default.args = defaultArgs;
-export const LargeDeletable = () => (
- ({})} />
+export const Deleteable = (args: ChipProps) => (
+ ({})} />
);
-
-export const LargeNotDeletable = () => (
-
-);
-
-export const SmallDeletable = () => (
- ({})} />
-);
-
-export const SmallNotDeletable = () => (
-
-);
-
-export const Outline = () => ;
+Deleteable.args = defaultArgs;
diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx
index 449d4a987b..ac341071bf 100644
--- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx
+++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import { DismissableBanner } from './DismissableBanner';
+import { DismissableBanner, Props } from './DismissableBanner';
import Typography from '@material-ui/core/Typography';
import { WebStorage } from '@backstage/core-app-api';
import {
@@ -29,6 +29,12 @@ import { Link } from '../Link';
export default {
title: 'Feedback/DismissableBanner',
component: DismissableBanner,
+ argTypes: {
+ variant: {
+ options: ['info', 'error', 'warning'],
+ control: { type: 'select' },
+ },
+ },
};
let errorApi: ErrorApi;
@@ -39,47 +45,27 @@ const createWebStorage = (): StorageApi => {
};
const apis = [[storageApiRef, createWebStorage()] as const];
+const defaultArgs = {
+ message: 'This is a dismissable banner',
+ variant: 'info',
+ fixed: false,
+};
-export const Default = () => (
+export const Default = (args: Props) => (