Merge remote-tracking branch 'origin/master' into task-action-idempotency

This commit is contained in:
bnechyporenko
2024-02-04 19:55:25 +01:00
85 changed files with 1140 additions and 935 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs-module-addons-contrib': patch
---
Fixed the value label text color in dark mode for the TextSize addon.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Bumped TypeScript to version `5.3`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend-module-aws-alb-provider': patch
---
Refactored to use the `jose` library for JWT handling.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-auth-node': patch
---
The helper function `makeProfileInfo` and `PassportHelpers.transformProfile`
were refactored to use the `jose` library.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Harmonize the package naming and allow custom prefix
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Ability for user to configure backstage token expiration
+10 -1
View File
@@ -1,6 +1,15 @@
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
labels: ['dependencies'],
extends: ['config:base', ':disableDependencyDashboard', ':gitSignOff'],
extends: ['config:best-practices', ':gitSignOff'],
// do not pin dev dependencies, which are part of the best-practices preset
ignorePresets: [':pinDevDependencies'],
// the default limit are 10 PRs
prConcurrentLimit: 20,
postUpdateOptions: ['yarnDedupeHighest'],
rangeStrategy: 'update-lockfile',
// @elastic/elasticsearch is ignored due to licensing issues. See #10992
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
- name: Docker metadata
id: meta
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
tags: type=raw,value=60d
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
python-version: '3.10'
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.2
uses: microsoft/setup-msbuild@v1.3.3
- name: Setup gyp env
run: |
-30
View File
@@ -1,30 +0,0 @@
---
id: glossary
title: Glossary
description: All Glossaries related to auth
---
- **Popup** - A separate browser window opened on top of the previous one.
- **OAuth** - More specifically OAuth 2.0, a standard protocol for
authorization. See [oauth.net/2/](https://oauth.net/2/).
- **OpenID Connect** - A layer on top of OAuth which standardises
authentication. See
[en.wikipedia.org/wiki/OpenID_Connect](https://en.wikipedia.org/wiki/OpenID_Connect).
- **JWT** - JSON Web Token, a popular JSON based token format that is commonly
encrypted and/or signed, see
[en.wikipedia.org/wiki/JSON_Web_Token](https://en.wikipedia.org/wiki/JSON_Web_Token)
- **Scope** - A string that describes a certain type of access that can be
granted to a user using OAuth.
- **Access token** - A token that gives access to perform actions on behalf of a
user. It will commonly have a short expiry time, and be limited to a set of
scopes. Part of the OAuth protocol.
- **ID token** - A JWT used to prove a user's identity, containing for example
the user's email. Part of OpenID Connect.
- **Offline access** - OAuth flow that results in both a refresh and access
token, where the refresh token has a long expiration or never expires, and can
be used to request more access tokens in the future. This lets the user go
"offline" with respect to the token issuer, but still be able to request more
tokens at a later time without further direct interaction for the user.
- **Code grant** - OAuth flow where the client receives an authorization code
that is passed to the backend to be exchanged for an access token and possibly
refresh token.
+7 -6
View File
@@ -258,7 +258,7 @@ When building CommonJS or ESM output, the build commands will always use
`src/index.ts` as the entrypoint. All non-relative modules imports are considered
external, meaning the Rollup build will only compile the source code of the package
itself. All import statements of external dependencies, even within the same
monorepo, will stay intact.
[monorepo](../references/glossary.md#monorepo), will stay intact.
The build of the type definitions works quite differently. The entrypoint of the
type definition build is the relative location of the package within the
@@ -307,11 +307,12 @@ support for them instead.
### Frontend Production
The frontend production bundling creates your typical web content bundle, all
contained within a single folder, ready for static serving. It is used when building
packages with the `'frontend'` role, and unlike the development bundling there is no way to
build a production bundle of an individual plugin. The output of the bundling
process is written to the `dist` folder in the package.
The frontend production bundling creates your typical web content
[bundle](../references/glossary.md#bundle), all contained within a single
folder, ready for static serving. It is used when building packages with the
`'frontend'` role, and unlike the development bundling there is no way to
build a production bundle of an individual plugin.
The output of the bundling process is written to the `dist` folder in the package.
Just like the development bundling, the production bundling is based on
[Webpack](https://webpack.js.org/). It uses the
+3 -17
View File
@@ -7,11 +7,12 @@ description: Overview of the Backstage CLI
## Introduction
A goal of Backstage is to provide a delightful developer experience in and
around the project. Creating new apps and plugins should be simple, iteration
around the project. Creating new [apps](../references/glossary.md#app) and
[plugins](../references/glossary.md#plugin) should be simple, iteration
speed should be fast, and the overhead of maintaining custom tooling should be
minimal. As a part of accomplishing this goal, Backstage provides its own build
system and tooling, delivered primarily through the
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) package. When
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) [package](../references/glossary.md#package). When
creating an app using
[`@backstage/create-app`](https://www.npmjs.com/package/@backstage/create-app),
you receive a project that's already prepared with a typical setup and package
@@ -36,18 +37,3 @@ The Backstage CLI intentionally does not provide many hooks for overriding or
customizing the build process. This is to allow for evolution of the CLI without
having to take a wide API surface into account. This allows us to iterate and
improve the tooling, as well as to more easily keep the system up to date.
## Glossary
- **Package** - A package in the Node.js ecosystem, often published to a package
registry such as [NPM](https://www.npmjs.com/).
- **Monorepo** - A project layout that consists of multiple packages within a
single project, where packages are able to have local dependencies on each
other. Often enabled through tooling such as [lerna](https://lerna.js.org/)
and [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)
- **Local Package** - One of the packages within a monorepo. These package may
or may not also be published to a package registry.
- **Bundle** - A collection of the deployment artifacts. The output of the
bundling process, which brings a collection of packages into a single
collection of deployment artifacts.
- **Package Role** - The declared role of a package, see [package roles](./cli-build-system.md#package-roles).
-26
View File
@@ -1,26 +0,0 @@
---
id: glossary
title: Backstage Glossary
# prettier-ignore
description: List of terms, abbreviations, and phrases used in Backstage, together with their explanations.
---
The Backstage Glossary lists terms, abbreviations, and phrases used in
Backstage, together with their explanations. We encourage you to use the
terminology below for clarity and consistency when discussing Backstage.
See also [Authentication Glossary](../auth/glossary.md), a separate glossary of terms and phrases
specifically related to the authentication and identity section of Backstage.
### Backstage User Profiles
There are three main user profiles for Backstage: the integrator, the
contributor, and the end user (typically a software engineer).
| Term | Explanation |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Integrator | The **integrator** hosts the Backstage app and configures which plugins are available to use in the app. |
| Contributor | The **contributor** adds functionality to the app by writing plugins. |
| End user | The **end user** uses the app's functionality and interacts with its plugins. This profile covers the various roles that help deliver software. The typical end user is a **software engineer**, but users might also consider themselves _designers_, _data scientists_, _product owners_, _engineering managers_, _technical writers_, and so on. |
| Software engineer | The **software engineer** is an **end user** who uses the app's functionality and interacts with its plugins in the course of writing and documenting code. This user is more likely to embed documentation in the code files they produce, and create rough drafts of conceptual pages in collaboration with a **technical writer** or _technical editor_. |
| Technical writer | The **technical writer** is an **end user** who uses the app's functionality and interacts with its plugins in the course of writing and editing documentation. This user is more likely to produce and customize templates and produce conceptual pages to supplement documentation embedded in code files. |
+1 -39
View File
@@ -6,11 +6,11 @@ description: Support and Community Details and Links
- [Discord chatroom](https://discord.gg/backstage-687207715902193673) - Get support or discuss the
project.
- [Stack Overflow](https://stackoverflow.com/questions/tagged/backstage) - Browse or ask questions on Stack Overflow.
- [Good First Issues](https://github.com/backstage/backstage/contribute) - Start
here if you want to contribute.
- [RFCs](https://github.com/backstage/backstage/labels/rfc) - Help shape the
technical direction by reviewing _Request for Comments_ issues.
- [BEPs](https://github.com/backstage/backstage/tree/master/beps#backstage-enhancement-proposals-beps) - A Backstage Enhancement Proposal (BEP) is a way to propose, communicate and coordinate on new efforts for the Backstage project.
- [FAQ](../faq/index.md) - Frequently Asked Questions.
- [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md) -
This is how we roll.
@@ -23,41 +23,3 @@ description: Support and Community Details and Links
## Community Hub
Check out the Backstage.io [Backstage Community Hub](https://backstage.io/community) for the Community Sessions, recordings, and community resources.
### Adding a recording to the meetup page
To add a new recording to the [meetup page](https://backstage.io/on-demand)
create a file in
[`microsite/data/on-demand`](https://github.com/backstage/backstage/tree/master/microsite/data/on-demand)
with your recording's information. Filenames should be in the format `yyyymmdd-xx.yaml`. The page will sort using the filename. Example file content:
```yaml
---
title: # name of the meetup
date: February 23, 2022 # date, format: Month day, year.
category: Meetup # Can be Event, Meetup, Webinar
description: # description, summary
youtubeUrl: # Url to youtube video
youtubeImgUrl: # Url to the preview image, for Youtube this is the format: https://i1.ytimg.com/vi/<YOUTUBE_ID>/mqdefault.jpg
```
### Adding an upcoming meetup to the meetup page
To add an upcoming meetup to the [meetup page](https://backstage.io/on-demand)
create a file in
[`microsite/data/on-demand`](https://github.com/backstage/backstage/tree/master/microsite/data/on-demand)
with your meetup's information. Filenames should be in the format `yyyymmdd-xx.yaml`, the page will sort using the filename. Example file content:
```yaml
---
title: # name of the meetup
date: February 23, 2022 # date, format: Month day, year.
category: Upcoming # Should be "Upcoming"
description: # description, summary
youtubeUrl: # Url to youtube video
youtubeImgUrl: # Url to the preview image, for Youtube this is the format: https://i1.ytimg.com/vi/<YOUTUBE_ID>/mqdefault.jpg
rsvpUrl: # Link to registration, calendar item, etc
eventUrl: # Link to event landing page
```
After the meetup is done, and the recording is ready you can change it to a meetup recording.
+6 -4
View File
@@ -6,11 +6,11 @@ description: A list of important permission framework concepts
### Permission
Any action that a user performs within Backstage may be represented as a permission. More complex actions, like executing a software template, may require authorization for multiple permissions throughout the flow. Permissions are identified by a unique name and optionally include a set of attributes that describe the corresponding action. Plugins are responsible for defining and exposing the permissions they enforce.
Any action that a user performs within Backstage may be represented as a permission. More complex actions, like executing a [software template](../references/glossary.md#software-templates), may require [authorization](../references/glossary.md#authorization) for multiple permissions throughout the flow. Permissions are identified by a unique name and optionally include a set of attributes that describe the corresponding action. [Plugins](../references/glossary.md#plugin) are responsible for defining and exposing the permissions they enforce as well as enforcing restrictions from the permission framework.
### Policy
User permissions are authorized by a central, user-defined permission policy. At a high level, a policy is a function that receives a Backstage user and permission, and returns a decision to allow or deny. Policies are expressed as code, which decouples the framework from any particular authorization model, like role-based access control (RBAC) or attribute-based access control (ABAC).
User [permissions](../references/glossary.md#permission-permission-plugin) are authorized by a central, user-defined permission policy. At a high level, a policy is a function that receives a Backstage user and permission, and returns a decision to allow or deny. Policies are expressed as code, which decouples the framework from any particular [authorization](../references/glossary.md#authorization) model, like role-based access control (RBAC) or attribute-based access control (ABAC).
### Policy decision versus enforcement
@@ -18,8 +18,10 @@ Two important responsibilities of any authorization system are to decide if a us
### Resources and rules
In many cases, a permission represents a user's interaction with another object. This object likely has information that policy authors can use to define more granular access. The permission framework introduces two abstractions to account for this: resources and rules. Resources represent the objects that users interact with. Rules are predicate-based controls that tap into a resource's data. For example, the catalog plugin defines a resource for catalog entities and a rule to check if an entity has a given annotation.
In many cases, a permission represents a user's interaction with another object. This object likely has information that policy authors can use to define more granular access. The permission framework introduces two abstractions to account for this: [resources](../references/glossary.md#resource-permission-plugin) and [rules](../references/glossary.md#rule-permission-plugin). For example, the catalog plugin defines a resource for catalog entities and a rule to check if an entity has a given annotation.
### Conditional decisions
Rules need additional data before they can be used in a decision. For example, the catalog plugin's "has annotation" rule needs to know what annotation to look for on a given entity. Once a rule is bound to relevant information it forms a condition. Conditions are then used to return a conditional decision from a policy. Conditional decisions tell the permission framework to delegate evaluation to the plugin that owns the corresponding resource. Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory.
[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](#plugin) that owns the corresponding [resource](#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true.
A good example would be the catalog plugin's "has annotation" rule which needs to know what annotation to look for on a given entity. The permission framework would respond to a request by the catalog plugin in this case with a condition decision. The catalog plugin would then need to correctly filter for entities matching the "has annotations" condition. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory.
+1 -1
View File
@@ -4,7 +4,7 @@ title: Defining custom permission rules
description: How to define custom permission rules for existing resources
---
For some use cases, you may want to define custom [rules](./concepts.md#resources-and-rules) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of.
For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of.
## Define a custom rule
@@ -4,9 +4,9 @@ title: 2. Adding a basic permission check
description: Explains how to add a basic permission check to a Backstage plugin
---
If the outcome of a permission check doesn't need to change for different [resources](../concepts.md#resources-and-rules), you can use a _basic permission check_. For this kind of check, we simply need to define a [permission](../concepts.md#resources-and-rules), and call `authorize` with it.
If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it.
For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../concepts.md#policy).
For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin).
We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation.
@@ -4,7 +4,7 @@ title: 3. Adding a resource permission check
description: Explains how to add a resource permission check to a Backstage plugin
---
When performing updates (or other operations) on specific [resources](../concepts.md#resources-and-rules), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise.
When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise.
## Creating the update permission
+314
View File
@@ -0,0 +1,314 @@
---
id: glossary
title: Glossary
# prettier-ignore
description: List of terms, abbreviations, and phrases used in Backstage, together with their explanations.
---
## Access Token
A [token](#token) that gives access to perform actions on behalf of a user. It will commonly have a short expiry time, and be limited to a set of [scopes](#scope). Part of the [OAuth](#oauth) protocol, see [their docs](https://oauth.net/2/access-tokens/) for more information.
## Administrator
Someone responsible for installing and maintaining a Backstage [app](#app) for an organization. A [user role](#user-role).
## API (catalog plugin)
An [entity](#entity) representing a schema that two [components](#component) use to communicate. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## App
An installed instance of Backstage. An app can be local, intended for a single development group or individual developer, or organizational, for use by an entire enterprise.
## Authorization Code
A type of [OAuth flow](#oauth) used by confidential and public clients to get an [access token](#access-token). See [the OAuth docs](https://oauth.net/2/grant-types/authorization-code/) for more details.
## Backstage
1. A platform for creating and deploying [developer portals](#developer-portal), originally created at Spotify. Backstage is an incubation-stage open source project of the [Cloud Native Computing Foundation](#cloud-native-computing-foundation).
2. [The Backstage Framework](#backstage-framework).
## Backstage Framework
The actual framework that Backstage [plugins](#plugin) sit on. This spans both the frontend and the backend, and includes core functionality such as declarative integration, config reading, database management, and many more.
## Bundle
1. A collection of [deployment artifacts](#deployment-artifacts).
2. The output of the bundling process, which brings a collection of [packages](#package) into a single collection of [deployment artifacts](#deployment-artifacts).
## Catalog
1. The core Backstage plugin that handle ingestion and display of your organizations software products.
2. An organization's portfolio of software products managed in Backstage.
## Cloud Native Computing
A set of technologies that "empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach." ([CNCF Cloud Native Definition v1.0](https://github.com/cncf/toc/blob/main/DEFINITION.md)).
## Cloud Native Computing Foundation
A foundation dedicated to the promotion and advancement of [Cloud Native Computing](#Cloud-Native-Computing). The mission of the Cloud Native Computing Foundation (CNCF) is "to make cloud native computing ubiquitous" ([CNCF Charter](https://github.com/cncf/foundation/blob/main/charter.md)).
CNCF is part of the [Linux Foundation](https://www.linuxfoundation.org/).
## CNCF
Cloud Native Computing Foundation.
## Code Grant
[OAuth](#oauth) flow where the client receives an [authorization code](#code) that is passed to the backend to be exchanged for an [access token](#access-token) and possibly a [refresh token](#refresh-token).
## Collator (search plugin)
A transformer that takes streams of [documents](#documents) and outputs searchable texts. They're usually responsible for the data transformation and definition and collection process for specific [documents](#documents).
## Component (catalog plugin)
A software product that is managed in the Backstage [Software Catalog](#software-catalog). A component can be a service, website, library, data pipeline, or any other piece of software managed as a single project. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## Condition (permission plugin)
A mapping from a given entity to criteria a user must fulfill to perform an action on that entity. Examples include `isOwner`, `hasRole`, etc.
## Conditional Decision (permission plugin)
A type of [decision](#policy-decision-permission-plugin) that allows for per-user evaluation of [conditions](#condition-permission-plugin) against a [resource](#resource-permission-plugin). See [Conditional Decisions](../permissions/concepts.md#conditional-decisions)
## Contributor
A volunteer who helps to improve an OSS product such as Backstage. This volunteer effort includes coding, testing, technical writing, user support, and other work. A [user role](#user-role).
## Declarative Integration
A new paradigm for Backstage frontend plugins, allowing definition in config files instead of hosting complete React pages. See [the Frontend System](https://backstage.io/docs/frontend-system).
## Decorator (search plugin)
A transform stream that allows you to add additional information to [documents](#document-search-plugin).
## Deployment Artifacts
An executable or package file with all of the necessary information required to deploy the application at runtime. Deployment artifacts can be hosted on [package registries](#package-registry).
## Developer
1. Someone who writes code and develops software.
2. A [user role](#user-role) defined as someone who uses a Backstage [app](#app). Might or might not actually be a software developer.
## Developer Portal
A centralized system comprising a user interface and database used to facilitate and document all the software projects within an organization. Backstage is both a developer portal and (by virtue of being based on plugins) a platform for creating developer portals.
## Document (search plugin)
An abstract concept representing something that can be found by searching for it. A document can represent a software entity, a TechDocs page, etc. Documents are made up of metadata fields, at a minimum -- a title, text, and location (as in a URL).
## Domain
An area that relates systems or entities to a business unit. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## Entity
What is cataloged in the Backstage Software Catalog. An entity is identified by a unique combination of [kind](#Kind), [namespace](#Namespace), and name. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## Evaluator
Someone who assesses whether Backstage is a suitable solution for their organization. The only [user role](#user-role) with a pre-deployment [use case](#use-case).
## ID Token
A [JWT](#jwt) used to prove a user's identity, containing for example the user's email. Part of [OpenID Connect](#openid-connect).
## Index (search plugin)
An index is a collection of [documents](#documents) of a given type.
## Indexer (search plugin)
A write stream of [documents](#documents).
## Integrator
Someone who develops one or more plugins that enable Backstage to interoperate with another software system. A [user role](#user-role).
## JWT
JSON Web Token.
A popular JSON based token format that is commonly encrypted and/or signed, see [the Wikipedia article](https://en.wikipedia.org/wiki/JSON_Web_Token) for more details.
## Kind
Classification of an [entity](#Entity) in the Backstage Software Catalog, for example _service_, _database_, and _team_.
## Kubernetes (CNCF Project)
An open-source system for automating deployment, scaling, and management of containerized applications.
## Kubernetes (Backstage plugin)
A core Backstage plugin enabling a service owner-focused view of Kubernetes resources.
## Local Package
One of the [packages](#package) within a [monorepo](#monorepo). These package may or may not also be published to a [package registry](#package-registry).
## Monorepo
1. A single repository for a collection of related software projects, such as all projects belonging to an organization.
2. A project layout that consists of multiple [packages](#package) within a single project, where packages are able to have local dependencies on each other. Often enabled through tooling such as [lerna](https://lerna.js.org/) and [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)
## Namespace (catalog plugin)
An optional attribute that can be used to organize [entities](#entity).
## Objective
A high level goal of a [user role](#User-Role) interacting with Backstage. Some goals of the _administrator_ user role, for example, are to maintain an instance ("app") of Backstage; to add and update functionality via plugins; and to troubleshoot issues.
## OAuth
Refers to: OAuth 2.0, a standard protocol for authorization. See [oauth.net/2/](https://oauth.net/2/).
## Offline Access
[OAuth](#oauth) flow that results in both a refresh token and [access token](#access-token), where the refresh token has a long expiration or never expires, and can be used to request more access tokens in the future. This lets the user go "offline" with respect to the token issuer, but still be able to request more tokens at a later time without further direct interaction for the user.
## OpenID Connect
A layer on top of [OAuth](#oauth) which standardises authentication. See [the Wikipedia article](https://en.wikipedia.org/wiki/OpenID_Connect) for more details.
## OSS
Open source software.
## Package
A package in the Node.js ecosystem, often published to a [package registry](#package-registry).
## Package Registry
A service that hosts [packages](#package). The most prominent example is [NPM](https://www.npmjs.com/).
## Package Role
The declared role of a package, see [package roles](../local-dev/cli-build-system.md#package-roles).
## Permission (core Backstage plugin)
A core Backstage plugin and framework that allows restriction of actions to specific users. See [their docs](https://backstage.io/docs/permissions/overview) for more information.
## Permission (permission plugin)
A restriction on any action that a user can perform against a specific [resource](#resource-permission-plugin) or set of resources. See [the permission framework docs](../permissions/concepts.md#permission) for more details.
## Persona (use cases)
Alternative term for a [User Role](#user-role).
## Plugin
A module in Backstage that adds a feature. All functionality outside of [the Backstage framework](#backstage-framework), even the core features, are implemented as plugins.
## Policy (permission plugin)
A construct that takes in a Backstage user and a [permission](#permission-permission-plugin) and returns a [policy decision](#policy-decision-permission-plugin).
## Policy Decision (permission plugin)
A specific response to a user's request to perform an action on a list of [resources](#resource-permission-plugin). Can be either `Approve`, `Deny` or [`Conditional`](#conditional-decision-permission-plugin).
## Popup
A separate browser window opened on top of the previous one.
## Procedure (use cases)
A set of actions that accomplish a goal, usually as part of a [use case](#Use-Case). A procedure can be high-level, containing other procedures, or can be as simple as a single [task](#Task).
## Query Translators (search plugin)
An abstraction layer between a search engine and the [Backstage Search](#search) backend. Allows for translation into queries against your search engine.
## Refresh token
A special token that an [OAuth](#oauth) client can use to get a new [access token](#access-token) when the latter expires.
https://oauth.net/2/refresh-tokens/
## Resource (catalog plugin)
An [entity](#entity) that represents a piece of physical or virtual infrastructure, for example a database, required by a component. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## Resource (permission plugin)
A representation of an object that a user interacts with and that can be permissioned. Not to be confused with [Software Catalog resources](#resource-catalog-plugin).
## Rule (permission plugin)
A predicate-based control that taps into a [resource](#resource-permission-plugin)'s data.
## Role
See [User Role](#User-Role).
## Scaffolder
Known as [Software Templates](#software-templates).
## Scope
A string that describes a certain type of access that can be granted to a user using OAuth, usually in conjunction with [access tokens](#access-token).
## Search
A Backstage plugin that provides a framework for searching a Backstage [app](#app), including the [Software Catalog](#Software-Catalog) and [TechDocs](#TechDocs). A core feature of Backstage.
## Search Engine (Backstage search)
Existing search technology that [Backstage Search](#search) can take advantage of through its modular design. Lunr is the default search in Backstage Search.
## Software Catalog
A Backstage plugin that provides a framework to keep track of ownership and metadata for any number and type of software [components](#component). A core feature of Backstage.
## Software Templates
A Backstage plugin with which to create [components](#component) in Backstage. A core feature of Backstage. Also known as the scaffolder.
## Software Template
A "skeleton" software project created and managed in the Backstage Software Templates tool.
## System (catalog plugin)
A system is a collection of [entities](#entity) that cooperate to perform a function. A system generally provides one or a few public APIs and consists of a handful of components, resources and private APIs. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
## Task (use cases)
A low-level step-by-step [Procedure](#Procedure).
## TechDocs
A documentation solution that manages and generates a technical documentation from Markdown files stored with software component code. A core feature of Backstage.
## Token
A string containing information.
## Use Case
A purpose for which a [user role](#User-Role) interacts with Backstage. Related to [Objective](#objective): An objective is _what_ the user wants to do; a use case is _how_ the user does it.
## User Role
A class of Backstage user for purposes of analyzing [use cases](#use-case). One of: evaluator; administrator; developer; integrator; and contributor.
@@ -0,0 +1,89 @@
<!-- This document is meant to solely serve as reference for how to write glossary entries. -->
## Entry format
A glossary entry should consist of two required things and two optional things,
1. a header,
2. a sentence defining what the thing is, and
3. an optional additional sentence or two giving more context into what the thing is and possible pointers on where to find more information, and possibly
4. links out to additional information.
### The header
The header (and first sentence) are the way users will discover your entry. The header has two parts,
1. The actual term, this should be as minimal as possible. You can fit more information into the body of the entry.
2. A disambiguator, this allows users to understand when certain entries are context specific or may have different meanings in different contexts.
### The term
Think of this as a dictionary. Single words are the base units and most definitions refer to a single word. Acronyms are acceptable. An adjective and a noun are also useful, `conditional decision`, `backstage framework`, etc. After 3 or _maybe_ 4 words, you should be trying to simplify and place more content in your entry instead.
In the title, your term should be in Title Case. It should also be in the singular.
### The disambiguator
The goal of a disambiguator is to differentiate terms that may have context specific meanings. This can have two interpretations, either
1. There are multiple terms and we need to create clear boundaries between their contexts, or
2. There are single terms that have meanings that are specific to a single context.
A good example for the first would be resources. Both the catalog plugin and permission plugin have the idea of resources, but they do not refer to the same thing.
A good example for the second would be `Query translators`. In our case, this refers to _search_ query translators, but it may refer to database query translators or the latter. By disambiguating early, we avoid confusion.
Beyond the above advice, there are no strong rules for when or when not to use a disambiguator. It is up to the entry writer and the reviewer.
Your disambiguator should be short, but need not be a single word -- examples include "use cases", "search plugin", "catalog plugin". When used the disambiguator should have the following form, `({disambiguator})` (a parenthesis enclosed term) and will sit to the right of the title. Your disambiguator should use lower case.
### Putting it together
Your title should look like `{word} ({disambiguator})`. Entries are not nested besides the disambiguator and should sit at `##`.
## The first sentence
Your first sentence should include the what for your word. Your goal should be to answer the question, "What is x?". Do _not_ use the word in your first sentence. If you are using other words in the glossary in your definition, you should reference them following [the Referencing section](#referencing).
If you have a term that could mean multiple things in the same context or the context is difficult to add boundaries for, you should separate each meaning into a separate section of the entry using an ordered list. For example,
```md
## Bundle
1. A deployment artifact.
2. A collection of packages.
```
## Additional sentences
You may not be able to fully define what you want in a single sentence. Use more sentences to flush out the meaning; however, if you start to get into the weeds, you should reconsider rehousing that information into a plugin specific "concepts" section. It's okay for words to be duplicated across both if the concepts section adds meaningful technical or architectural discussion.
## Linking out to additional resources
If the term you're defining has a better or more in depth source for that information, link to it. This can include plugin specific concept documents, external documentation, or core framework documentation.
You should format these links as
```md
See [the glossary](./glossary.md) for more details.
```
. Additional links beyond the first one should be appended with `and` or `or` as necessary.
## Putting it all together
```md
## Component (catalog plugin)
A software product that is managed in the Backstage [Software Catalog](#software-catalog). A component can be a service, website, library, data pipeline, or any other piece of software managed as a single project. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information.
```
## Referencing
### In the glossary
You should reference often. Words are defined recursively, especially in tech and un-nesting some terms requires additional glossary items. It's okay if your terms require multiple other terms to build on. Your goal with referencing is to provide small reusable words that you can trust users know the definition of.
### In the text
You should reference (and create a new entry if it doesn't exist) whenever you see a new word that a reasonable reader may not know. If you've already added a reference in your current passage -- in the glossary this will be your entry -- don't add a new reference. References should point initially to the glossary and if there is additional information (like a concepts page), the glossary should have a link to that page.
+1 -1
View File
@@ -19,7 +19,7 @@ By default, the `UnifiedThemeProvider` is already used. If you add a custom them
themes: [
{
// ...
provider: ({ children }) => (
Provider: ({ children }) => (
- <ThemeProvider theme={lightTheme}>.
- <CssBaseline>{children}</CssBaseline>.
- </ThemeProvider
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: November 17, 2021
category: Meetup
description: At this months adopters session, get the scoop on Spotifys plan for paid plugins, a first look at Boxs DevPortal, and answers to big questions, like “How will I know if Backstage will work at a large company like mine?”
youtubeUrl: https://youtu.be/apCDT3_DmFk
youtubeImgUrl: https://i1.ytimg.com/vi/apCDT3_DmFk/mqdefault.jpg
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: November 18, 2021
category: Meetup
description: In the contributors track for this months community session, hear about upcoming deprecations, the fruits of Hacktoberfest, the future of both the Kubernetes plugin and the Tech Insights plugin, tips on contributing, and more.
youtubeUrl: https://youtu.be/n1OWGwYAOiI
youtubeImgUrl: https://i1.ytimg.com/vi/n1OWGwYAOiI/mqdefault.jpg
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: December 15, 2021
category: Meetup
description: At our final adopters session of 2021, the community received a grab bag of end-of-year treats, including eye-catching infographics, a presentation from finance startup Brex about their path to adopting Backstage, and tips on branding your Backstage portal. Plus, get your official Backstage Zoom background. But first up, a quick look back at the year that was.
youtubeUrl: https://youtu.be/0QMQYSTKAx0
youtubeImgUrl: https://i1.ytimg.com/vi/0QMQYSTKAx0/mqdefault.jpg
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: December 16, 2021
category: Meetup
description: At our final contributors session of 2021, see how the new Backstage Upgrade Helper works and enjoy smoother upgrades, hear updates from the maintainers, and meet our contributor of the month. As in the adopters session, first we celebrate the years milestones and share a few resources you can use to spread the word about Backstage.
youtubeUrl: https://youtu.be/nYjI2j-lWEM
youtubeImgUrl: https://i1.ytimg.com/vi/nYjI2j-lWEM/mqdefault.jpg
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: February 16, 2022
category: Meetup
description: This community session marks one year of Backstage community sessions! In this session, we celebrate one year of the Backstage community, hear from Patrik on stabilizing core APIs, learn more about Homepage Templates, find hidden info about your catalog entities, and Q&A.
youtubeUrl: https://youtu.be/evf_LV0KzIk
youtubeImgUrl: https://i1.ytimg.com/vi/evf_LV0KzIk/mqdefault.jpg
-7
View File
@@ -1,7 +0,0 @@
---
title: Community Sessions
date: February 23, 2022
category: Meetup
description: Community Sessions Anniversary , SWAG opportunity❗, TechDocs add-on framework, URL Reader demo 👨‍💻, Q&A
youtubeUrl: https://youtu.be/Buu_KWdIFwU
youtubeImgUrl: https://i1.ytimg.com/vi/Buu_KWdIFwU/mqdefault.jpg
-9
View File
@@ -1,9 +0,0 @@
---
title: Adopter Community Sessions
date: March 16, 2022
category: Meetup
description: In this community session, we celebrate Backstages 2nd birthday and a few other milestones that Backstage reached in March! The amazing Suzanne Daniels also put together a panel from some of our community members to discuss all things developer experience!
youtubeUrl: https://youtu.be/2s98-sxJT1c
youtubeImgUrl: https://i1.ytimg.com/vi/2s98-sxJT1c/mqdefault.jpg
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
eventUrl: https://github.com/backstage/community/issues
-9
View File
@@ -1,9 +0,0 @@
---
title: Contributor Community Sessions
date: March 23, 2022
category: Meetup
description: In this Community Session, we review the recent milestones our Backstage community hit, chat with Djamaile Rahamat, our contributor spotlight, and watch three demos ranging from new plugins to test environments.
youtubeUrl: https://youtu.be/BAzxljI765U
youtubeImgUrl: https://i1.ytimg.com/vi/BAzxljI765U/mqdefault.jpg
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
eventUrl: https://github.com/backstage/community/issues
-9
View File
@@ -1,9 +0,0 @@
---
title: Adopters Community Sessions
date: April 20, 2022
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
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
eventUrl: https://github.com/backstage/community/issues/44
-9
View File
@@ -1,9 +0,0 @@
---
title: Contributor Community Sessions
date: April 27, 2022
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
rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com
eventUrl: https://github.com/backstage/community/issues/44
-9
View File
@@ -1,9 +0,0 @@
---
title: Adopters Community Sessions
date: May 18, 2022
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/dEd1fl3wRvo
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
-9
View File
@@ -1,9 +0,0 @@
---
title: Contributor Community Sessions
date: May 25, 2022
category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/neNipVE5ffY
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
-9
View File
@@ -1,9 +0,0 @@
---
title: Adopters Community Sessions
date: June 15, 2022
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/aKZnjnE5Wy8
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/49
-9
View File
@@ -1,9 +0,0 @@
---
title: Contributor Community Sessions
date: June 22, 2022
category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/E-jWqWXBxUY
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/49
-9
View File
@@ -1,9 +0,0 @@
---
title: Adopters Community Sessions
date: July 20, 2022
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/4VFNlPxWcx8
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/52
-9
View File
@@ -1,9 +0,0 @@
---
title: Contributor Community Sessions
date: July 27, 2022
category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/pNLLrNN_hkE
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/52
-7
View File
@@ -1,7 +0,0 @@
---
title: Adopters Community Sessions
date: August 17, 2022
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/qYnvc8ge1kg
youtubeImgUrl: https://backstage.io/img/b-sessions.png
-7
View File
@@ -1,7 +0,0 @@
---
title: Contributor Community Sessions
date: August 24, 2022
category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/8ydEFFiuHAc
youtubeImgUrl: https://backstage.io/img/b-sessions.png
-7
View File
@@ -1,7 +0,0 @@
---
title: Adopters Community Sessions
date: September 21, 2022
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/K44RQAVWWnY
youtubeImgUrl: https://backstage.io/img/b-sessions.png
-7
View File
@@ -1,7 +0,0 @@
---
title: Contributor Community Sessions
date: September 28, 2022
category: Meetup
description: Join the maintainers and contributors for the Contributor Community Sessions
youtubeUrl: https://youtu.be/jmNT5x3mKaQ
youtubeImgUrl: https://backstage.io/img/b-sessions.png
@@ -1,6 +1,6 @@
---
title: GitHub Codespaces
author: Aditya Singhal
author: Aditya Singhal - Lab45
authorUrl: https://github.com/adityasinghal26
category: Development
description: Integrates GitHub Codespaces for a Backstage component with the Authenticated User.
+8
View File
@@ -144,6 +144,14 @@ module.exports = {
from: '/docs/features/software-templates/testing-scaffolder-alpha',
to: '/docs/features/software-templates/migrating-to-rjsf-v5',
},
{
from: '/docs/auth/glossary',
to: '/docs/references/glossary',
},
{
from: '/docs/overview/glossary',
to: '/docs/references/glossary',
},
],
},
],
+1 -1
View File
@@ -25,7 +25,7 @@
"@types/webpack-env": "^1.18.0",
"js-yaml": "^4.1.0",
"prettier": "^2.6.2",
"typescript": "~5.0.0",
"typescript": "~5.1.0",
"yaml-loader": "^0.8.0"
},
"prettier": "@spotify/prettier-config",
+3 -4
View File
@@ -37,7 +37,6 @@
"overview/versioning-policy",
"overview/threat-model",
"overview/support",
"overview/glossary",
"overview/logos"
],
"Getting Started": [
@@ -318,8 +317,7 @@
"auth/add-auth-provider",
"auth/service-to-service-auth",
"auth/autologout",
"auth/troubleshooting",
"auth/glossary"
"auth/troubleshooting"
],
"Permissions": [
"permissions/overview",
@@ -479,6 +477,7 @@
"architecture-decisions/adrs-adr013"
],
"FAQ": ["faq/index", "faq/product", "faq/technical"],
"Accessibility": ["accessibility/index"]
"Accessibility": ["accessibility/index"],
"References": ["references/glossary"]
}
}
+2 -2
View File
@@ -42,8 +42,8 @@ const Community = () => {
{
title: 'Community sessions',
content:
'Maintainers and adopters meet monthly to share updates, demos, and ideas. Yep, all sessions are recorded!',
link: '/on-demand',
'Maintainers and adopters meet monthly to share updates, demos, and ideas. You can find recorded session on our YouTube channel!',
link: 'https://github.com/backstage/community/tree/main/backstage-community-sessions#backstage-community-sessions',
label: 'Join a session',
},
{
@@ -1,66 +0,0 @@
import Link from '@docusaurus/Link';
import { SimpleCard } from '@site/src/components/simpleCard/simpleCard';
import React from 'react';
export interface IOnDemandData {
title: string;
category: string;
description: string;
date: string;
youtubeUrl: string;
youtubeImgUrl: string;
rsvpUrl: string;
eventUrl: string;
}
export const OnDemandCard = ({
title,
category,
description,
date,
youtubeUrl,
youtubeImgUrl,
rsvpUrl,
eventUrl,
}: IOnDemandData) => (
<SimpleCard
header={
<>
<h3>{title}</h3>
<p className="PluginCardAuthor">on {date}</p>
<span className="badge badge--primary">{category}</span>
<img src={youtubeImgUrl} alt={title} />
</>
}
body={<p>{description}</p>}
footer={
category.toLowerCase() === 'upcoming' ? (
<>
<Link
to={eventUrl}
className="button button--outline button--sm button--primary"
>
Event page
</Link>
<Link
to={rsvpUrl}
className="button button--outline button--sm button--primary"
>
Remind me
</Link>
</>
) : (
<Link
to={youtubeUrl}
className="button button--outline button--primary button--block"
>
Watch on YouTube
</Link>
)
}
/>
);
-78
View File
@@ -1,78 +0,0 @@
import Layout from '@theme/Layout';
import clsx from 'clsx';
import React from 'react';
import { IOnDemandData, OnDemandCard } from './_onDemandCard';
import pluginsStyles from './onDemand.module.scss';
import { truncateDescription } from '@site/src/util/truncateDescription';
import Link from '@docusaurus/Link';
//#region Plugin data import
const onDemandContext = require.context(
'../../../data/on-demand',
false,
/\.ya?ml/,
);
const onDemandData = onDemandContext.keys().reduce(
(acum, id) => {
const pluginData: IOnDemandData = onDemandContext(id).default;
acum[
pluginData.category === 'Upcoming' ? 'upcomingEvents' : 'onDemandEvents'
].push(truncateDescription(pluginData));
return acum;
},
{
upcomingEvents: [] as IOnDemandData[],
onDemandEvents: [] as IOnDemandData[],
},
);
//#endregion
const Plugins = () => (
<Layout>
<div
className={clsx('container', 'padding--lg', pluginsStyles.onDemandPage)}
>
<div className="communityBanner">
<div className="communityContent">
<h2>Community sessions</h2>
<p>
Upcoming events and recorded sessions about updates, demos and
discussions.
</p>
</div>
<Link
to="/docs/overview/support"
className="button button--outline button--primary"
>
Add an event or recording
</Link>
</div>
<div className="bulletLine margin-bottom--lg"></div>
<h2>Upcoming live events</h2>
<div className="cardsContainer margin-bottom--lg">
{onDemandData.upcomingEvents.map(eventData => (
<OnDemandCard key={eventData.title} {...eventData}></OnDemandCard>
))}
</div>
<h2>Community on demand</h2>
<div className="cardsContainer margin-bottom--lg">
{onDemandData.onDemandEvents.map(eventData => (
<OnDemandCard key={eventData.title} {...eventData}></OnDemandCard>
))}
</div>
</div>
</Layout>
);
export default Plugins;
@@ -1,44 +0,0 @@
.onDemandPage {
:global(.communityBanner) {
display: flex;
align-items: center;
}
:global(.communityContent) {
flex: 1;
}
:global(.card) {
max-width: 100%;
}
:global(.card .card__header) {
display: grid;
row-gap: 0.25rem;
column-gap: 1rem;
justify-items: start;
> * {
margin: 0;
}
:global(img) {
width: 250px;
max-width: 100%;
justify-self: center;
}
}
:global(.card .card__footer) {
gap: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
:global(.cardsContainer) {
gap: 1rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
justify-items: start;
}
}
-143
View File
@@ -1,143 +0,0 @@
.VideoCard {
background-color: #282828;
height: 100%;
padding: 16px;
display: flex;
flex-direction: column;
}
.VideoGrid {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 1fr;
}
@media (max-width: 1200px) {
.VideoGrid {
grid-template-columns: repeat(3, 1fr);
}
}
@media only screen and (max-width: 815px) {
.VideoGrid {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (max-width: 485px) {
.VideoGrid {
grid-template-columns: 1fr;
}
}
.VideoCard img {
float: left;
margin: 0px 16px 8px 0px;
height: 160px;
width: 300px;
}
.VideoCardHeader {
display: flex;
flex-direction: row;
align-items: center;
max-height: fit-content;
min-height: fit-content;
}
.VideoCardImage {
width: 200px;
height: 80px;
margin-right: 16px;
}
.VideoCardImage img {
width: 100%;
max-width: 100%;
}
.VideoCardTitle {
color: white;
vertical-align: top;
margin: 8px 0 0;
}
.VideoCardInfo {
flex: 1;
}
.VideoAddNewButton {
position: absolute;
bottom: 16px;
right: 0px;
}
@media only screen and (max-width: 485px) {
.VideoAddNewButton {
bottom: -4px;
}
}
.VideoButtonFilled {
padding: 4px 8px;
border-radius: 4px;
color: #69ddc7;
}
.VideoButtonFilled:hover {
border: 1px solid #69ddc7;
background-color: transparent;
}
.VideoCardChipOutlined {
font-size: small;
border-radius: 16px;
padding: 2px 8px;
border: 1px solid #69ddc7;
color: #69ddc7;
}
.VideoCardFooter {
display: flex;
justify-content: flex-end;
align-items: flex-end;
margin-top: auto;
min-height: 2em;
}
.VideoCardFooter a {
padding: 2px 8px;
}
.VideoPageLayout {
margin: auto;
max-width: 1430px;
padding: 20px;
}
.VideoPageHeader {
position: relative;
}
.VideoPageHeader h2 {
display: inline-block;
}
.VideoCardBody {
padding-top: 8px;
}
.VideoCardDate,
.VideoCardDate a {
margin-bottom: 0.25em;
color: rgba(255, 255, 255, 0.6);
}
.VideoCardDate a:hover {
color: white;
}
#add-video-card {
border: 1px solid #69ddc7;
}
+9 -9
View File
@@ -3907,7 +3907,7 @@ __metadata:
react-dom: ^18.0.0
sass: ^1.57.1
swc-loader: ^0.2.3
typescript: ~5.0.0
typescript: ~5.1.0
yaml-loader: ^0.8.0
languageName: unknown
linkType: soft
@@ -11692,23 +11692,23 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:~5.0.0":
version: 5.0.4
resolution: "typescript@npm:5.0.4"
"typescript@npm:~5.1.0":
version: 5.1.6
resolution: "typescript@npm:5.1.6"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 82b94da3f4604a8946da585f7d6c3025fff8410779e5bde2855ab130d05e4fd08938b9e593b6ebed165bda6ad9292b230984f10952cf82f0a0ca07bbeaa08172
checksum: b2f2c35096035fe1f5facd1e38922ccb8558996331405eb00a5111cc948b2e733163cc22fab5db46992aba7dd520fff637f2c1df4996ff0e134e77d3249a7350
languageName: node
linkType: hard
"typescript@patch:typescript@~5.0.0#~builtin<compat/typescript>":
version: 5.0.4
resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin<compat/typescript>::version=5.0.4&hash=a1c5e5"
"typescript@patch:typescript@~5.1.0#~builtin<compat/typescript>":
version: 5.1.6
resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin<compat/typescript>::version=5.1.6&hash=a1c5e5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 6a1fe9a77bb9c5176ead919cc4a1499ee63e46b4e05bf667079f11bf3a8f7887f135aa72460a4c3b016e6e6bb65a822cb8689a6d86cbfe92d22cc9f501f09213
checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606
languageName: node
linkType: hard
+2 -2
View File
@@ -18,7 +18,6 @@ nav:
- Release & Versioning Policy: 'overview/versioning-policy.md'
- Backstage Threat Model: 'overview/threat-model.md'
- Support and community: 'overview/support.md'
- Glossary: 'overview/glossary.md'
- Logo assets: 'overview/logos.md'
- Getting Started:
- Getting Started: 'getting-started/index.md'
@@ -171,7 +170,6 @@ nav:
- Contributing New Providers: 'auth/add-auth-provider.md'
- Service to Service Auth: 'auth/service-to-service-auth.md'
- Troubleshooting Auth: 'auth/troubleshooting.md'
- Glossary: 'auth/glossary.md'
- Deployment:
- Deploying Backstage: 'deployment/index.md'
- Scaling: 'deployment/scaling.md'
@@ -218,3 +216,5 @@ nav:
- Overview: 'faq/index.md'
- Product FAQ: 'faq/product.md'
- Technical FAQ: 'faq/technical.md'
- References:
- Glossary: 'references/glossary.md'
+1 -1
View File
@@ -93,7 +93,7 @@
"semver": "^7.5.3",
"shx": "^0.3.2",
"ts-node": "^10.4.0",
"typescript": "~5.2.0"
"typescript": "~5.1.0"
},
"prettier": "@spotify/prettier-config",
"lint-staged": {
@@ -19,7 +19,7 @@ import chalk from 'chalk';
import camelCase from 'lodash/camelCase';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { addPackageDependency, Task } from '../../tasks';
import {
moduleIdIdPrompt,
@@ -27,6 +27,7 @@ import {
pluginIdPrompt,
} from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -45,9 +46,11 @@ export const backendModule = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id: pluginId, moduleId } = options;
const dirName = `${pluginId}-backend-module-${moduleId}`;
const name = ctx.scope
? `@${ctx.scope}/plugin-${dirName}`
: `backstage-plugin-${dirName}`;
const name = resolvePackageName({
baseName: dirName,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating backend module ${chalk.cyan(name)}`);
@@ -19,10 +19,11 @@ import chalk from 'chalk';
import camelCase from 'lodash/camelCase';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { addPackageDependency, Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -40,9 +41,11 @@ export const backendPlugin = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const pluginId = `${id}-backend`;
const name = ctx.scope
? `@${ctx.scope}/plugin-${pluginId}`
: `backstage-plugin-${pluginId}`;
const name = resolvePackageName({
baseName: pluginId,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating backend plugin ${chalk.cyan(name)}`);
@@ -0,0 +1,78 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { resolvePackageName } from './util';
describe('resolvePackageName', () => {
it('should generate correct name without scope', () => {
expect(resolvePackageName({ baseName: 'test', plugin: true })).toEqual(
'backstage-plugin-test',
);
expect(resolvePackageName({ baseName: 'test', plugin: false })).toEqual(
'test',
);
});
it('should generate correct name for backstage scope', () => {
expect(
resolvePackageName({
baseName: 'test',
scope: 'backstage',
plugin: true,
}),
).toEqual('@backstage/plugin-test');
expect(
resolvePackageName({
baseName: 'test',
scope: 'backstage',
plugin: false,
}),
).toEqual('@backstage/test');
});
it('should generate correct name for custom scope', () => {
expect(
resolvePackageName({
baseName: 'test',
scope: 'custom',
plugin: true,
}),
).toEqual('@custom/backstage-plugin-test');
expect(
resolvePackageName({
baseName: 'test',
scope: 'custom',
plugin: false,
}),
).toEqual('@custom/test');
});
it('should generate correct name for custom scope and custom prefix', () => {
expect(
resolvePackageName({
baseName: 'test',
scope: 'custom/myapp.',
plugin: true,
}),
).toEqual('@custom/myapp.backstage-plugin-test');
expect(
resolvePackageName({
baseName: 'test',
scope: 'custom/myapp.',
plugin: false,
}),
).toEqual('@custom/myapp.test');
});
});
@@ -0,0 +1,38 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const resolvePackageName = (options: {
baseName: string;
scope?: string;
plugin: boolean;
}) => {
const { baseName, scope, plugin } = options;
if (scope) {
if (plugin) {
const pluginName = scope.startsWith('backstage')
? 'plugin'
: 'backstage-plugin';
return scope.includes('/')
? `@${scope}${pluginName}-${baseName}`
: `@${scope}/${pluginName}-${baseName}`;
}
return scope.includes('/')
? `@${scope}${baseName}`
: `@${scope}/${baseName}`;
}
return plugin ? `backstage-plugin-${baseName}` : baseName;
};
@@ -180,7 +180,7 @@ const router = (
fs.readJson(mockDir.resolve('packages/app/package.json')),
).resolves.toEqual({
dependencies: {
'@internal/plugin-test': '^1.0.0',
'@internal/backstage-plugin-test': '^1.0.0',
},
});
@@ -188,7 +188,7 @@ const router = (
fs.readFile(mockDir.resolve('packages/app/src/App.tsx'), 'utf8'),
).resolves.toBe(`
import { createApp } from '@backstage/app-defaults';
import { TestPage } from '@internal/plugin-test';
import { TestPage } from '@internal/backstage-plugin-test';
const router = (
<FlatRoutes>
@@ -20,10 +20,11 @@ import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { addPackageDependency, Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -41,9 +42,11 @@ export const frontendPlugin = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const name = ctx.scope
? `@${ctx.scope}/plugin-${id}`
: `backstage-plugin-${id}`;
const name = resolvePackageName({
baseName: id,
scope: ctx.scope,
plugin: true,
});
const extensionName = `${upperFirst(camelCase(id))}Page`;
Task.log();
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -37,7 +38,11 @@ export const nodeLibraryPackage = createFactory<Options>({
optionsPrompts: [pluginIdPrompt(), ownerPrompt()],
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const name = ctx.scope ? `@${ctx.scope}/${id}` : `${id}`;
const name = resolvePackageName({
baseName: id,
scope: ctx.scope,
plugin: false,
});
Task.log();
Task.log(`Creating node-library package ${chalk.cyan(name)}`);
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -38,9 +39,11 @@ export const pluginCommon = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const suffix = `${id}-common`;
const name = ctx.scope
? `@${ctx.scope}/plugin-${suffix}`
: `backstage-plugin-${suffix}`;
const name = resolvePackageName({
baseName: suffix,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating backend plugin ${chalk.cyan(name)}`);
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -38,9 +39,11 @@ export const pluginNode = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const suffix = `${id}-node`;
const name = ctx.scope
? `@${ctx.scope}/plugin-${suffix}`
: `backstage-plugin-${suffix}`;
const name = resolvePackageName({
baseName: suffix,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating Node.js plugin library ${chalk.cyan(name)}`);
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -38,9 +39,11 @@ export const pluginWeb = createFactory<Options>({
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const suffix = `${id}-react`;
const name = ctx.scope
? `@${ctx.scope}/plugin-${suffix}`
: `backstage-plugin-${suffix}`;
const name = resolvePackageName({
baseName: suffix,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating web plugin library ${chalk.cyan(name)}`);
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -55,14 +56,11 @@ export const scaffolderModule = createFactory<Options>({
const { id } = options;
const slug = `scaffolder-backend-module-${id}`;
let name = `backstage-plugin-${slug}`;
if (ctx.scope) {
if (ctx.scope === 'backstage') {
name = `@backstage/plugin-${slug}`;
} else {
name = `@${ctx.scope}/backstage-plugin-${slug}`;
}
}
const name = resolvePackageName({
baseName: slug,
scope: ctx.scope,
plugin: true,
});
Task.log();
Task.log(`Creating module ${chalk.cyan(name)}`);
@@ -17,10 +17,11 @@
import chalk from 'chalk';
import { paths } from '../../paths';
import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners';
import { createFactory, CreateContext } from '../types';
import { CreateContext, createFactory } from '../types';
import { Task } from '../../tasks';
import { ownerPrompt, pluginIdPrompt } from './common/prompts';
import { executePluginPackageTemplate } from './common/tasks';
import { resolvePackageName } from './common/util';
type Options = {
id: string;
@@ -37,7 +38,11 @@ export const webLibraryPackage = createFactory<Options>({
optionsPrompts: [pluginIdPrompt(), ownerPrompt()],
async create(options: Options, ctx: CreateContext) {
const { id } = options;
const name = ctx.scope ? `@${ctx.scope}/${id}` : `${id}`;
const name = resolvePackageName({
baseName: id,
scope: ctx.scope,
plugin: false,
});
Task.log();
Task.log(`Creating web-library package ${chalk.cyan(name)}`);
@@ -33,23 +33,23 @@ import { SidebarPinStateProvider } from './SidebarPinStateContext';
export type SidebarPageClassKey = 'root';
const useStyles = makeStyles<
Theme,
{ sidebarConfig: SidebarConfig; isPinned: boolean }
>(
type StyleProps = { sidebarConfig: SidebarConfig; isPinned: boolean };
const useStyles = makeStyles<Theme, StyleProps>(
theme => ({
root: {
width: '100%',
transition: 'padding-left 0.1s ease-out',
isolation: 'isolate',
[theme.breakpoints.up('sm')]: {
paddingLeft: props =>
paddingLeft: (props: StyleProps) =>
props.isPinned
? props.sidebarConfig.drawerWidthOpen
: props.sidebarConfig.drawerWidthClosed,
},
[theme.breakpoints.down('xs')]: {
paddingBottom: props => props.sidebarConfig.mobileSidebarHeight,
paddingBottom: (props: StyleProps) =>
props.sidebarConfig.mobileSidebarHeight,
},
'@media print': {
padding: '0px !important',
@@ -39,7 +39,7 @@
"lerna": "^7.3.0",
"node-gyp": "^9.0.0",
"prettier": "^2.3.2",
"typescript": "~5.2.0"
"typescript": "~5.3.0"
},
"resolutions": {
"@types/react": "^18",
@@ -38,14 +38,15 @@
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"jose": "^4.6.0",
"jwt-decode": "^3.1.0",
"node-cache": "^5.1.2"
"node-cache": "^5.1.2",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"express": "^4.18.2"
"express": "^4.18.2",
"msw": "^2.0.8"
},
"files": [
"dist"
@@ -14,36 +14,30 @@
* limitations under the License.
*/
import express from 'express';
import { SignJWT } from 'jose';
import {
ALB_ACCESS_TOKEN_HEADER,
ALB_JWT_HEADER,
awsAlbAuthenticator,
} from './authenticator';
import { jwtVerify } from 'jose';
import express from 'express';
import { AuthenticationError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { AuthenticationError } from '@backstage/errors';
const jwtMock = jwtVerify as jest.Mocked<any>;
const mockJwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWV9JRCIsImlzcyI6IklTU1VFUl9VUkwifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlVzZXIgTmFtZSIsImlhdCI6MTUxNjIzOTAyMn0.uMCSBGhij1xn5pnot8XgD-huQuTIBOFGs6kkW_p_X94';
const mockAccessToken = 'ACCESS_TOKEN';
const mockClaims = {
sub: '1234567890',
name: 'User Name',
family_name: 'Name',
given_name: 'User',
picture: 'PICTURE_URL',
email: 'user.name@email.test',
exp: 1632833763,
iss: 'ISSUER_URL',
};
jest.mock('jose');
beforeEach(() => {
jest.clearAllMocks();
});
describe('AwsAlbProvider', () => {
const mockAccessToken = 'ACCESS_TOKEN';
const mockClaims = {
sub: '1234567890',
name: 'User Name',
family_name: 'Name',
given_name: 'User',
picture: 'PICTURE_URL',
email: 'user.name@email.test',
exp: Date.now() + 10000,
iss: 'ISSUER_URL',
};
const signingKey = new TextEncoder().encode('signingKey');
let mockJwt: string;
const mockRequest = {
header: jest.fn(name => {
if (name === ALB_JWT_HEADER) {
@@ -54,6 +48,16 @@ describe('AwsAlbProvider', () => {
return undefined;
}),
} as unknown as express.Request;
const mockRequestWithInvalidJwt = {
header: jest.fn(name => {
if (name === ALB_JWT_HEADER) {
return 'invalid.jwt';
} else if (name === ALB_ACCESS_TOKEN_HEADER) {
return mockAccessToken;
}
return undefined;
}),
} as unknown as express.Request;
const mockRequestWithoutJwt = {
header: jest.fn(name => {
if (name === ALB_ACCESS_TOKEN_HEADER) {
@@ -71,13 +75,20 @@ describe('AwsAlbProvider', () => {
}),
} as unknown as express.Request;
beforeEach(async () => {
mockJwt = await new SignJWT(mockClaims)
.setProtectedHeader({ alg: 'HS256' })
.sign(signingKey);
});
describe('should transform to type AwsAlbResponse', () => {
it('when JWT is valid and identity is resolved successfully', async () => {
jwtMock.mockReturnValueOnce(Promise.resolve({ payload: mockClaims }));
const response = await awsAlbAuthenticator.authenticate(
{ req: mockRequest },
{ issuer: 'ISSUER_URL', getKey: jest.fn() },
{
issuer: 'ISSUER_URL',
getKey: jest.fn().mockResolvedValue(signingKey),
},
);
expect(response).toEqual({
result: {
@@ -119,27 +130,38 @@ describe('AwsAlbProvider', () => {
});
it('JWT is invalid', async () => {
jwtMock.mockImplementationOnce(() => {
throw new Error('bad JWT');
});
await expect(
awsAlbAuthenticator.authenticate(
{ req: mockRequest },
{ req: mockRequestWithInvalidJwt },
{ issuer: 'ISSUER_URL', getKey: jest.fn() },
),
).rejects.toThrow(
'Exception occurred during JWT processing: Error: bad JWT',
'Exception occurred during JWT processing: JWSInvalid: Invalid Compact JWS',
);
});
it('issuer is missing', async () => {
jwtMock.mockReturnValueOnce({});
const jwt = await new SignJWT({})
.setProtectedHeader({ alg: 'HS256' })
.sign(signingKey);
const req = {
header: jest.fn(name => {
if (name === ALB_JWT_HEADER) {
return jwt;
} else if (name === ALB_ACCESS_TOKEN_HEADER) {
return mockAccessToken;
}
return undefined;
}),
} as unknown as express.Request;
await expect(
awsAlbAuthenticator.authenticate(
{ req: mockRequest },
{ issuer: 'ISSUER_URL', getKey: jest.fn() },
{ req },
{
issuer: 'ISSUER_URL',
getKey: jest.fn().mockResolvedValue(signingKey),
},
),
).rejects.toThrow(
'Exception occurred during JWT processing: AuthenticationError: Issuer mismatch on JWT token',
@@ -147,14 +169,27 @@ describe('AwsAlbProvider', () => {
});
it('issuer is invalid', async () => {
jwtMock.mockReturnValueOnce({
iss: 'INVALID_ISSUE_URL',
});
const jwt = await new SignJWT({ iss: 'INVALID_ISSUER_URL' })
.setProtectedHeader({ alg: 'HS256' })
.sign(signingKey);
const req = {
header: jest.fn(name => {
if (name === ALB_JWT_HEADER) {
return jwt;
} else if (name === ALB_ACCESS_TOKEN_HEADER) {
return mockAccessToken;
}
return undefined;
}),
} as unknown as express.Request;
await expect(
awsAlbAuthenticator.authenticate(
{ req: mockRequest },
{ issuer: 'ISSUER_URL', getKey: jest.fn() },
{ req },
{
issuer: 'ISSUER_URL',
getKey: jest.fn().mockResolvedValue(signingKey),
},
),
).rejects.toThrow(
'Exception occurred during JWT processing: AuthenticationError: Issuer mismatch on JWT token',
@@ -1,10 +1,3 @@
import NodeCache from 'node-cache';
import { makeProfileInfo, provisionKeyCache } from './helpers';
import * as crypto from 'crypto';
import { JWTHeaderParameters } from 'jose';
import { PassportProfile } from '@backstage/plugin-auth-node';
import jwtDecoder from 'jwt-decode';
/*
* Copyright 2020 The Backstage Authors
*
@@ -21,40 +14,47 @@ import jwtDecoder from 'jwt-decode';
* limitations under the License.
*/
const mockKey = async () => {
return `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnuN4LlaJhaUpx+qZFTzYCrSBLk0I
yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw==
-----END PUBLIC KEY-----
`;
};
import * as crypto from 'crypto';
import { JWTHeaderParameters, UnsecuredJWT } from 'jose';
import NodeCache from 'node-cache';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import { PassportProfile } from '@backstage/plugin-auth-node';
import { makeProfileInfo, provisionKeyCache } from './helpers';
jest.mock('crypto');
const cryptoMock = crypto as jest.Mocked<any>;
jest.mock('node-fetch', () => ({
__esModule: true,
default: async () => {
return {
text: async () => {
return mockKey();
},
};
},
}));
const jwtMock = jwtDecoder as jest.Mocked<any>;
jest.mock('jwt-decode');
describe('helpers', () => {
const server = setupServer();
setupRequestMockHandlers(server);
const nodeCache = jest.fn() as unknown as NodeCache;
nodeCache.set = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
server.use(
http.get(
'https://public-keys.auth.elb.eu-west-1.amazonaws.com/kid',
() =>
new HttpResponse(
`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnuN4LlaJhaUpx+qZFTzYCrSBLk0I
yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw==
-----END PUBLIC KEY-----
`,
),
),
);
});
it('should create a key', () => {
const getKey = provisionKeyCache('eu-west-1', nodeCache);
expect(getKey).toBeDefined();
});
it('should return a key from cache', async () => {
const getKey = provisionKeyCache('eu-west-1', nodeCache);
@@ -65,6 +65,7 @@ describe('helpers', () => {
expect(key).toBe('key');
});
it('should update cache if key is not found', async () => {
const getKey = provisionKeyCache('eu-west-1', nodeCache);
@@ -77,6 +78,7 @@ describe('helpers', () => {
await getKey({ kid: 'kid' } as unknown as JWTHeaderParameters);
expect(nodeCache.set).toHaveBeenCalledWith('kid', 'key');
});
it('should throw error if key is not found', async () => {
const getKey = provisionKeyCache('eu-west-1', nodeCache);
@@ -87,6 +89,7 @@ describe('helpers', () => {
getKey({ kid: 'kid' } as unknown as JWTHeaderParameters),
).rejects.toThrow();
});
it('should throw if key is not present in request header', async () => {
const getKey = provisionKeyCache('eu-west-1', nodeCache);
@@ -119,19 +122,19 @@ describe('makeProfileInfo', () => {
};
expect(makeProfileInfo(profile, accessToken)).toEqual(result);
});
it('should return profile info from id token', () => {
jwtMock.mockReturnValueOnce({
email: 'email',
picture: 'picture',
name: 'displayName',
});
const profile = {
name: {
familyName: 'familyName',
givenName: 'givenName',
},
} as PassportProfile;
const idToken = 'idToken';
const idToken = new UnsecuredJWT({
email: 'email',
picture: 'picture',
name: 'displayName',
}).encode();
const result = {
email: 'email',
picture: 'picture',
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PassportProfile } from '@backstage/plugin-auth-node/';
import { ProfileInfo } from '@backstage/plugin-auth-node';
import { KeyObject } from 'crypto';
import jwtDecoder from 'jwt-decode';
import NodeCache from 'node-cache';
import * as crypto from 'crypto';
import { JWTHeaderParameters } from 'jose';
import { JWTHeaderParameters, decodeJwt } from 'jose';
import NodeCache from 'node-cache';
import fetch from 'node-fetch';
import { PassportProfile, ProfileInfo } from '@backstage/plugin-auth-node';
import { AuthenticationError } from '@backstage/errors';
export const makeProfileInfo = (
@@ -45,7 +44,11 @@ export const makeProfileInfo = (
if ((!email || !picture || !displayName) && idToken) {
try {
const decoded: Record<string, string> = jwtDecoder(idToken);
const decoded: Record<string, string> = decodeJwt(idToken) as {
email?: string;
picture?: string;
name?: string;
};
if (!email && decoded.email) {
email = decoded.email;
}
+11
View File
@@ -165,3 +165,14 @@ To try out SAML, you can use the mock identity provider:
## Links
- [The Backstage homepage](https://backstage.io)
## Configuring Token Expiration in App Config
If you need to change Backstage token expiration from the default value of one hour you can do so through configuration. Note that this is **not** the session duration, but rather the duration that the short-term cryptographic tokens are valid for. The expiration can not be set lower than 10 minutes or above 24 hours.
This is what the configuration looks like:
```
auth:
backstageTokenExpiration: { minutes: <user_defined_value> }
```
+6
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
/** Configuration options for the auth plugin */
auth?: {
@@ -184,6 +186,10 @@ export interface Config {
cfaccess?: {
teamName: string;
};
/**
* The backstage token expiration.
*/
backstageTokenExpiration?: HumanDuration;
};
};
}
+1 -2
View File
@@ -51,6 +51,7 @@
"@backstage/plugin-auth-backend-module-okta-provider": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/types": "workspace:^",
"@google-cloud/firestore": "^7.0.0",
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
@@ -64,7 +65,6 @@
"fs-extra": "10.1.0",
"google-auth-library": "^8.0.0",
"jose": "^4.6.0",
"jwt-decode": "^3.1.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
@@ -93,7 +93,6 @@
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
"@types/jwt-decode": "^3.1.0",
"@types/passport-auth0": "^1.0.5",
"@types/passport-github2": "^1.2.4",
"@types/passport-google-oauth20": "^2.0.3",
@@ -15,17 +15,118 @@
*/
import express from 'express';
import { UnsecuredJWT } from 'jose';
import passport from 'passport';
import { InternalOAuthError } from 'passport-oauth2';
import {
executeRedirectStrategy,
executeFrameHandlerStrategy,
executeRefreshTokenStrategy,
makeProfileInfo,
} from './PassportStrategyHelper';
import { PassportProfile } from './types';
const mockRequest = {} as unknown as express.Request;
describe('PassportStrategyHelper', () => {
describe('makeProfileInfo', () => {
it('retrieves email from passport profile', () => {
const profile: PassportProfile = {
emails: [{ value: 'email' }],
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(profile);
expect(profileInfo.email).toEqual('email');
});
it('retrieves picture from passport profile avatarUrl', () => {
const profile: PassportProfile = {
avatarUrl: 'avatarUrl',
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(profile);
expect(profileInfo.picture).toEqual('avatarUrl');
});
it('falls back to picture from passport profile photos field', () => {
const profile: PassportProfile = {
photos: [{ value: 'picture' }],
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(profile);
expect(profileInfo.picture).toEqual('picture');
});
it('falls back to email from ID token', async () => {
const profile: PassportProfile = {
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(
profile,
await new UnsecuredJWT({ email: 'email' }).encode(),
);
expect(profileInfo.email).toEqual('email');
});
it('falls back to picture from ID token', async () => {
const profile: PassportProfile = {
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(
profile,
await new UnsecuredJWT({ picture: 'picture' }).encode(),
);
expect(profileInfo.picture).toEqual('picture');
});
it('falls back to name from ID token', async () => {
const profile: PassportProfile = {
provider: '',
id: '',
displayName: '',
};
const profileInfo = makeProfileInfo(
profile,
await new UnsecuredJWT({ name: 'name' }).encode(),
);
expect(profileInfo.displayName).toEqual('name');
});
it('fails when attempting to fall back to invalid JWT', () => {
const profile: PassportProfile = {
provider: '',
id: '',
displayName: '',
};
expect(() => makeProfileInfo(profile, 'invalid JWT')).toThrow(
'Failed to parse id token and get profile info',
);
});
});
class MyCustomRedirectStrategy extends passport.Strategy {
authenticate() {
this.redirect('a', 302);
@@ -16,7 +16,7 @@
import express from 'express';
import passport from 'passport';
import jwtDecoder from 'jwt-decode';
import { decodeJwt } from 'jose';
import { InternalOAuthError } from 'passport-oauth2';
import { PassportProfile } from './types';
@@ -51,7 +51,11 @@ export const makeProfileInfo = (
if ((!email || !picture || !displayName) && idToken) {
try {
const decoded: Record<string, string> = jwtDecoder(idToken);
const decoded = decodeJwt(idToken) as {
email?: string;
name?: string;
picture?: string;
};
if (!email && decoded.email) {
email = decoded.email;
}
@@ -1,19 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// BACKSTAGE_SESSION_EXPIRATION the default session expiration time
// TODO: find a less hard-coded way to access this, perhaps by reading it from the configuration.
export const BACKSTAGE_SESSION_EXPIRATION = 3600;
@@ -1,17 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { BACKSTAGE_SESSION_EXPIRATION } from './constants';
@@ -0,0 +1,77 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { readBackstageTokenExpiration } from './readBackstageTokenExpiration';
describe('Test for default backstage token expiry time', () => {
it('Will return default backstage session expiration', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
});
expect(readBackstageTokenExpiration(config)).toBe(3600);
});
it('Will return user defined 120 minutes as backstage session expiration', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
auth: {
backstageTokenExpiration: { minutes: 120 },
},
});
expect(readBackstageTokenExpiration(config)).toBe(7200);
});
it('Will return minimum duration of 10 minutes as backstage session expiration', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
auth: {
backstageTokenExpiration: { minutes: 2 },
},
});
expect(readBackstageTokenExpiration(config)).toBe(600);
});
it('Will return user configured value as backstage session expiration', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
auth: {
backstageTokenExpiration: { minutes: 20 },
},
});
expect(readBackstageTokenExpiration(config)).toBe(1200);
});
it('Will return maximum of 24 hour as backstage session expiration if user configured value is more than a day', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
auth: {
backstageTokenExpiration: { minutes: 1500 },
},
});
expect(readBackstageTokenExpiration(config)).toBe(86400);
});
});
@@ -0,0 +1,44 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RootConfigService } from '@backstage/backend-plugin-api';
import { readDurationFromConfig } from '@backstage/config';
import { durationToMilliseconds } from '@backstage/types';
const TOKEN_EXP_DEFAULT_S = 3600;
const TOKEN_EXP_MIN_S = 600;
const TOKEN_EXP_MAX_S = 86400;
export function readBackstageTokenExpiration(config: RootConfigService) {
const processingIntervalKey = 'auth.backstageTokenExpiration';
if (!config.has(processingIntervalKey)) {
return TOKEN_EXP_DEFAULT_S;
}
const duration = readDurationFromConfig(config, {
key: processingIntervalKey,
});
const durationS = Math.round(durationToMilliseconds(duration) / 1000);
if (durationS < TOKEN_EXP_MIN_S) {
return TOKEN_EXP_MIN_S;
} else if (durationS > TOKEN_EXP_MAX_S) {
return TOKEN_EXP_MAX_S;
}
return durationS;
}
+5 -6
View File
@@ -29,7 +29,6 @@ import {
} from '@backstage/backend-common';
import { assertError, NotFoundError } from '@backstage/errors';
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { createOidcRouter, TokenFactory, KeyStores } from '../identity';
import session from 'express-session';
import connectSessionKnex from 'connect-session-knex';
@@ -37,10 +36,11 @@ import passport from 'passport';
import { Minimatch } from 'minimatch';
import { CatalogAuthResolverContext } from '../lib/resolvers';
import { AuthDatabase } from '../database/AuthDatabase';
import { BACKSTAGE_SESSION_EXPIRATION } from '../lib/session';
import { readBackstageTokenExpiration } from './readBackstageTokenExpiration';
import { TokenIssuer } from '../identity/types';
import { StaticTokenIssuer } from '../identity/StaticTokenIssuer';
import { StaticKeyStore } from '../identity/StaticKeyStore';
import { Config } from '@backstage/config';
/** @public */
export type ProviderFactories = { [s: string]: AuthProviderFactory };
@@ -76,9 +76,8 @@ export async function createRouter(
const appUrl = config.getString('app.baseUrl');
const authUrl = await discovery.getExternalBaseUrl('auth');
const backstageTokenExpiration = readBackstageTokenExpiration(config);
const authDb = AuthDatabase.create(database);
const sessionExpirationSeconds = BACKSTAGE_SESSION_EXPIRATION;
const keyStore = await KeyStores.fromConfig(config, {
logger,
@@ -91,7 +90,7 @@ export async function createRouter(
{
logger: logger.child({ component: 'token-factory' }),
issuer: authUrl,
sessionExpirationSeconds: sessionExpirationSeconds,
sessionExpirationSeconds: backstageTokenExpiration,
},
keyStore as StaticKeyStore,
);
@@ -99,7 +98,7 @@ export async function createRouter(
tokenIssuer = new TokenFactory({
issuer: authUrl,
keyStore,
keyDurationSeconds: sessionExpirationSeconds,
keyDurationSeconds: backstageTokenExpiration,
logger: logger.child({ component: 'token-factory' }),
algorithm:
tokenFactoryAlgorithm ??
@@ -15,6 +15,7 @@
*/
import { Request } from 'express';
import { decodeJwt } from 'jose';
import { Strategy } from 'passport';
import { PassportProfile } from './types';
import { ProfileInfo } from '../types';
@@ -27,30 +28,6 @@ interface InternalOAuthError extends Error {
};
}
/** @internal */
function decodeJwtPayload(token: string): Record<string, string> {
const payloadStr = token.split('.')[1];
if (!payloadStr) {
throw new Error('Invalid JWT token');
}
let payload: unknown;
try {
payload = JSON.parse(
Buffer.from(
payloadStr.replace(/-/g, '+').replace(/_/g, '/'),
'base64',
).toString('utf8'),
);
} catch (e) {
throw new Error('Invalid JWT token');
}
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
throw new Error('Invalid JWT token');
}
return payload as Record<string, string>;
}
/** @public */
export class PassportHelpers {
private constructor() {}
@@ -78,7 +55,11 @@ export class PassportHelpers {
if ((!email || !picture || !displayName) && idToken) {
try {
const decoded: Record<string, string> = decodeJwtPayload(idToken);
const decoded = decodeJwt(idToken) as {
email?: string;
name?: string;
picture?: string;
};
if (!email && decoded.email) {
email = decoded.email;
}
+8 -8
View File
@@ -317,42 +317,42 @@ function LinearProgressWithLabel(props: {
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_BRANCH_NAME_CALVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_BRANCH_NAME_CALVER = 'rc/2020.01.01_1';
const MOCK_RELEASE_BRANCH_NAME_CALVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_BRANCH_NAME_SEMVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_BRANCH_NAME_SEMVER = 'rc/1.2.3';
const MOCK_RELEASE_BRANCH_NAME_SEMVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_CANDIDATE_TAG_NAME_CALVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_CANDIDATE_TAG_NAME_CALVER = 'rc-2020.01.01_1';
const MOCK_RELEASE_CANDIDATE_TAG_NAME_CALVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_CANDIDATE_TAG_NAME_SEMVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_CANDIDATE_TAG_NAME_SEMVER = 'rc-1.2.3';
const MOCK_RELEASE_CANDIDATE_TAG_NAME_SEMVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_NAME_CALVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_NAME_CALVER = 'Version 2020.01.01_1';
const MOCK_RELEASE_NAME_CALVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_NAME_SEMVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_NAME_SEMVER = 'Version 1.2.3';
const MOCK_RELEASE_NAME_SEMVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_VERSION_TAG_NAME_CALVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_VERSION_TAG_NAME_CALVER = 'version-2020.01.01_1';
const MOCK_RELEASE_VERSION_TAG_NAME_CALVER: string;
// Warning: (ae-missing-release-tag) "MOCK_RELEASE_VERSION_TAG_NAME_SEMVER" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
const MOCK_RELEASE_VERSION_TAG_NAME_SEMVER = 'version-1.2.3';
const MOCK_RELEASE_VERSION_TAG_NAME_SEMVER: string;
// Warning: (ae-missing-release-tag) "mockBumpedTag" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -70,7 +70,7 @@ const StyledSlider = withStyles(theme => ({
left: '50%',
transform: 'scale(1) translate(-50%, -5px) !important',
'& *': {
color: theme.palette.common.black,
color: theme.palette.textSubtle,
fontSize: theme.typography.caption.fontSize,
background: 'transparent',
},
+67 -87
View File
@@ -4677,8 +4677,9 @@ __metadata:
"@backstage/plugin-auth-node": "workspace:^"
express: ^4.18.2
jose: ^4.6.0
jwt-decode: ^3.1.0
msw: ^2.0.8
node-cache: ^5.1.2
node-fetch: ^2.6.7
languageName: unknown
linkType: soft
@@ -4908,12 +4909,12 @@ __metadata:
"@backstage/plugin-auth-backend-module-okta-provider": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-catalog-node": "workspace:^"
"@backstage/types": "workspace:^"
"@google-cloud/firestore": ^7.0.0
"@types/body-parser": ^1.19.0
"@types/cookie-parser": ^1.4.2
"@types/express": ^4.17.6
"@types/express-session": ^1.17.2
"@types/jwt-decode": ^3.1.0
"@types/passport": ^1.0.3
"@types/passport-auth0": ^1.0.5
"@types/passport-github2": ^1.2.4
@@ -4932,7 +4933,6 @@ __metadata:
fs-extra: 10.1.0
google-auth-library: ^8.0.0
jose: ^4.6.0
jwt-decode: ^3.1.0
knex: ^3.0.0
lodash: ^4.17.21
luxon: ^3.0.0
@@ -11402,9 +11402,9 @@ __metadata:
languageName: node
linkType: hard
"@graphiql/react@npm:^0.20.0, @graphiql/react@npm:^0.20.2":
version: 0.20.2
resolution: "@graphiql/react@npm:0.20.2"
"@graphiql/react@npm:^0.20.0, @graphiql/react@npm:^0.20.2, @graphiql/react@npm:^0.20.3":
version: 0.20.3
resolution: "@graphiql/react@npm:0.20.3"
dependencies:
"@graphiql/toolkit": ^0.9.1
"@headlessui/react": ^1.7.15
@@ -11425,7 +11425,7 @@ __metadata:
graphql: ^15.5.0 || ^16.0.0
react: ^16.8.0 || ^17 || ^18
react-dom: ^16.8.0 || ^17 || ^18
checksum: 76bd00fd144b1e3044e3239d80fbda49795e5eb41d222da373af11819d06f657a430eff6853e93724134538f3c5e619e0ecf9111b0818fe14569edb290106c84
checksum: e8b362bd67ff5499c8db64097f9a4050782be53420e27b4c84ad1eba99c9615984257b7a19d0fd9c6f72ce92366321014b04762b400403ee52f53248dd7a8785
languageName: node
linkType: hard
@@ -14359,13 +14359,13 @@ __metadata:
linkType: hard
"@playwright/test@npm:^1.32.3":
version: 1.41.1
resolution: "@playwright/test@npm:1.41.1"
version: 1.41.2
resolution: "@playwright/test@npm:1.41.2"
dependencies:
playwright: 1.41.1
playwright: 1.41.2
bin:
playwright: cli.js
checksum: d9877e777a1a7f60f097df57b6abc2478e2ae342930a409c8546c8aa40d6e206cbc16bf1c71b23414ac3fbad36dcae1ad79635d7f4eb705ab54d3c705e82ea04
checksum: 87d9e725106111b2af1b2dec32454cd2a2d9665ff735669dc751caa30240e6db595ecfb9422719fa65dcff6ca19dea93ac2ae70d587efddde31def0754549d4c
languageName: node
linkType: hard
@@ -18154,14 +18154,14 @@ __metadata:
linkType: hard
"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5":
version: 4.17.41
resolution: "@types/express-serve-static-core@npm:4.17.41"
version: 4.17.43
resolution: "@types/express-serve-static-core@npm:4.17.43"
dependencies:
"@types/node": "*"
"@types/qs": "*"
"@types/range-parser": "*"
"@types/send": "*"
checksum: 12750f6511dd870bbaccfb8208ad1e79361cf197b147f62a3bedc19ec642f3a0f9926ace96705f4bc88ec2ae56f61f7ca8c2438e6b22f5540842b5569c28a121
checksum: 08e940cae52eb1388a7b5f61d65f028e783add77d1854243ae920a6a2dfb5febb6acaafbcf38be9d678b0411253b9bc325893c463a93302405f24135664ab1e4
languageName: node
linkType: hard
@@ -18373,12 +18373,12 @@ __metadata:
linkType: hard
"@types/jest@npm:*, @types/jest@npm:^29.0.0, @types/jest@npm:^29.5.11":
version: 29.5.11
resolution: "@types/jest@npm:29.5.11"
version: 29.5.12
resolution: "@types/jest@npm:29.5.12"
dependencies:
expect: ^29.0.0
pretty-format: ^29.0.0
checksum: f892a06ec9f0afa9a61cd7fa316ec614e21d4df1ad301b5a837787e046fcb40dfdf7f264a55e813ac6b9b633cb9d366bd5b8d1cea725e84102477b366df23fdd
checksum: 19b1efdeed9d9a60a81edc8226cdeae5af7479e493eaed273e01243891c9651f7b8b4c08fc633a7d0d1d379b091c4179bbaa0807af62542325fd72f2dd17ce1c
languageName: node
linkType: hard
@@ -18485,15 +18485,6 @@ __metadata:
languageName: node
linkType: hard
"@types/jwt-decode@npm:^3.1.0":
version: 3.1.0
resolution: "@types/jwt-decode@npm:3.1.0"
dependencies:
jwt-decode: "*"
checksum: 82ff0b3826e5d9da48be1f11e16fec96e56dd946995edaa100682202b9b7beb30fb04a353cbabd378d3b13a24241b48a0b662a4f181bae7f758147d92368d930
languageName: node
linkType: hard
"@types/keyv@npm:*, @types/keyv@npm:^3.1.1":
version: 3.1.4
resolution: "@types/keyv@npm:3.1.4"
@@ -19013,13 +19004,13 @@ __metadata:
linkType: hard
"@types/react@npm:^18":
version: 18.2.48
resolution: "@types/react@npm:18.2.48"
version: 18.2.52
resolution: "@types/react@npm:18.2.52"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: c9ca43ed2995389b7e09492c24e6f911a8439bb8276dd17cc66a2fbebbf0b42daf7b2ad177043256533607c2ca644d7d928fdfce37a67af1f8646d2bac988900
checksum: 4abc9bd63879e57c3df43a9cacff54c079e21b61ef934d33801e0177c26f582aa7d1d88a0769a740a4fd273168e3b150ff61de23e0fa85d1070e82ddce2b7fd2
languageName: node
linkType: hard
@@ -19310,12 +19301,12 @@ __metadata:
linkType: hard
"@types/tar@npm:^6.1.1":
version: 6.1.10
resolution: "@types/tar@npm:6.1.10"
version: 6.1.11
resolution: "@types/tar@npm:6.1.11"
dependencies:
"@types/node": "*"
minipass: ^4.0.0
checksum: 82d46f1246e830b98833ed94fbdfbcb3ffb8a5d7173609622d56c9326124523a3cc541607876c63a6ab9117eb59fb37ef8a6284b194164ab1d1d8c03a8ba59c6
checksum: 9b79f61f9179db65ecd3f5e9c0d2152839ad13381d39c38c3a9408aa1f3a2b061ba195e7d758be1863294a0ce69df6659f0e3e09f440c9f5309bded58bc87c89
languageName: node
linkType: hard
@@ -27048,8 +27039,8 @@ __metadata:
linkType: hard
"express-openapi-validator@npm:^5.0.4":
version: 5.1.2
resolution: "express-openapi-validator@npm:5.1.2"
version: 5.1.3
resolution: "express-openapi-validator@npm:5.1.3"
dependencies:
"@apidevtools/json-schema-ref-parser": ^9.1.2
"@types/multer": ^1.4.7
@@ -27066,7 +27057,7 @@ __metadata:
multer: ^1.4.5-lts.1
ono: ^7.1.3
path-to-regexp: ^6.2.0
checksum: e02eaad8549893f874916cfc52a9d81f1ef15c553e726876e6b73cc93469a21e28e42d1d25449aa04c764f8d024b1ea664b7ce6083abdd8513168ece7929ee20
checksum: c99785e6bf3072086a671e854369e338bce2a20fea65d2a4445dac6c40ac632e67dc91d9e0edeb4cce5e739124b5d5742bcf2447c09b3085dc76e1ba89a71ca0
languageName: node
linkType: hard
@@ -28713,7 +28704,7 @@ __metadata:
languageName: node
linkType: hard
"graphiql@npm:3.1.0, graphiql@npm:^3.0.6":
"graphiql@npm:3.1.0":
version: 3.1.0
resolution: "graphiql@npm:3.1.0"
dependencies:
@@ -28729,6 +28720,22 @@ __metadata:
languageName: node
linkType: hard
"graphiql@npm:^3.0.6":
version: 3.1.1
resolution: "graphiql@npm:3.1.1"
dependencies:
"@graphiql/react": ^0.20.3
"@graphiql/toolkit": ^0.9.1
graphql-language-service: ^5.2.0
markdown-it: ^12.2.0
peerDependencies:
graphql: ^15.5.0 || ^16.0.0
react: ^16.8.0 || ^17 || ^18
react-dom: ^16.8.0 || ^17 || ^18
checksum: fa0e6a6854b688a80d2d560c07c042c4d63a45ab1ebdb5b56a081a5a2aea6f77b2ef10afb73e071bbb22eb293048a9b72760e91459fe66704afce56271b13ba5
languageName: node
linkType: hard
"graphlib@npm:^2.1.8":
version: 2.1.8
resolution: "graphlib@npm:2.1.8"
@@ -32366,13 +32373,6 @@ __metadata:
languageName: node
linkType: hard
"jwt-decode@npm:*, jwt-decode@npm:^3.1.0":
version: 3.1.2
resolution: "jwt-decode@npm:3.1.2"
checksum: 20a4b072d44ce3479f42d0d2c8d3dabeb353081ba4982e40b83a779f2459a70be26441be6c160bfc8c3c6eadf9f6380a036fbb06ac5406b5674e35d8c4205eeb
languageName: node
linkType: hard
"kafkajs@npm:^2.0.0":
version: 2.2.4
resolution: "kafkajs@npm:2.2.4"
@@ -32717,22 +32717,22 @@ __metadata:
linkType: hard
"lint-staged@npm:^15.0.0":
version: 15.2.0
resolution: "lint-staged@npm:15.2.0"
version: 15.2.1
resolution: "lint-staged@npm:15.2.1"
dependencies:
chalk: 5.3.0
commander: 11.1.0
debug: 4.3.4
execa: 8.0.1
lilconfig: 3.0.0
listr2: 8.0.0
listr2: 8.0.1
micromatch: 4.0.5
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.3.4
bin:
lint-staged: bin/lint-staged.js
checksum: 4fb178b8d3ff454f7874697dfbd41017630f61a06296d12ac9dfd578d078c70aff7108b67fab38af94896ef2740a1e7541c1512d0d3c688ed90e6c3af3530f0d
checksum: d2b0361b311cb3384e58a5d4bfd5d34e6ff6ad41e5ea5dcc87c65379ea6e280c84f0a037df147355963dcf4d3dc1487795e3132c38e1bc5511b25889f405e189
languageName: node
linkType: hard
@@ -32743,9 +32743,9 @@ __metadata:
languageName: node
linkType: hard
"listr2@npm:8.0.0":
version: 8.0.0
resolution: "listr2@npm:8.0.0"
"listr2@npm:8.0.1":
version: 8.0.1
resolution: "listr2@npm:8.0.1"
dependencies:
cli-truncate: ^4.0.0
colorette: ^2.0.20
@@ -32753,7 +32753,7 @@ __metadata:
log-update: ^6.0.0
rfdc: ^1.3.0
wrap-ansi: ^9.0.0
checksum: 5cb110a710d14488c71d2207fc5141256abb1f21cbe5ebc12177ae640f94e040a1ef8c031b70ff9f24c4a8fa57c0825a54b534e52bdfaffc122a81082faae8ed
checksum: 4dfeabfa037b3981d0edbf30789971ba727ba4cfcc13051ceaff7a1b3d26509ef2d946015c65c600b0775ec9d1ef58a81937d94c9c03de464b654f429cc7c3ed
languageName: node
linkType: hard
@@ -37103,27 +37103,27 @@ __metadata:
languageName: node
linkType: hard
"playwright-core@npm:1.41.1":
version: 1.41.1
resolution: "playwright-core@npm:1.41.1"
"playwright-core@npm:1.41.2":
version: 1.41.2
resolution: "playwright-core@npm:1.41.2"
bin:
playwright-core: cli.js
checksum: c83446a560c6bd85f6f0cd586ff8c643b77e2005567386e12f85890936cc370673114b94cd883246018797cc1580e93b0296ade7d07275bb611b8962f5bb9693
checksum: b41ede0db3fd3e3f7e0b0efbdfb2dbc4db345e113cf9c4451af21d1d5b5d9ab5e969f5662852925e37b2198ae5daab92aa48108fe3d4eb81c849ba8752aaf8cc
languageName: node
linkType: hard
"playwright@npm:1.41.1":
version: 1.41.1
resolution: "playwright@npm:1.41.1"
"playwright@npm:1.41.2":
version: 1.41.2
resolution: "playwright@npm:1.41.2"
dependencies:
fsevents: 2.3.2
playwright-core: 1.41.1
playwright-core: 1.41.2
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 3da7fb929abdec6adbdd8829f840580f5f210713214a8d230b130127f2270403eb2113c6c1418012221149707250fff896794c7c22c260dd09a92bf800227f31
checksum: acf166003ec42cd795f5fca096c5135880d78e84ec2d0a1911b2cab984cf75dc06e50d3aa24b56cbcbc5369ca8c61831e76c5f8674531a272fbd0f6e624fa387
languageName: node
linkType: hard
@@ -39050,12 +39050,12 @@ __metadata:
linkType: hard
"react-virtualized-auto-sizer@npm:^1.0.11":
version: 1.0.21
resolution: "react-virtualized-auto-sizer@npm:1.0.21"
version: 1.0.22
resolution: "react-virtualized-auto-sizer@npm:1.0.22"
peerDependencies:
react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
checksum: 9c0929a0363b3b3b10ad65ac57d7562a20966a9c38c28c10dfd296d0a6a3e678319dce2384c5177f43965c2b7f226f5766deffc4b7eaaab0ee7c2cfe0c7d6b48
checksum: dc3fc29437b7179de71f77e5be3514e8789944132d7eb03f7157f783ec167ad9bebf1d371c135cf74fc5787dfac313a2914a5d23b45e978ae77be36c673342e5
languageName: node
linkType: hard
@@ -40136,7 +40136,7 @@ __metadata:
semver: ^7.5.3
shx: ^0.3.2
ts-node: ^10.4.0
typescript: ~5.2.0
typescript: ~5.1.0
languageName: unknown
linkType: soft
@@ -43198,16 +43198,6 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:~5.2.0":
version: 5.2.2
resolution: "typescript@npm:5.2.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c
languageName: node
linkType: hard
"typescript@patch:typescript@~5.0.4#~builtin<compat/typescript>":
version: 5.0.4
resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin<compat/typescript>::version=5.0.4&hash=a1c5e5"
@@ -43228,16 +43218,6 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@~5.2.0#~builtin<compat/typescript>":
version: 5.2.2
resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin<compat/typescript>::version=5.2.2&hash=a1c5e5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 07106822b4305de3f22835cbba949a2b35451cad50888759b6818421290ff95d522b38ef7919e70fb381c5fe9c1c643d7dea22c8b31652a717ddbd57b7f4d554
languageName: node
linkType: hard
"ua-parser-js@npm:^0.7.30":
version: 0.7.33
resolution: "ua-parser-js@npm:0.7.33"
@@ -45386,11 +45366,11 @@ __metadata:
linkType: hard
"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4":
version: 3.22.3
resolution: "zod-to-json-schema@npm:3.22.3"
version: 3.22.4
resolution: "zod-to-json-schema@npm:3.22.4"
peerDependencies:
zod: ^3.22.4
checksum: 2747a3d1514f579006939c0edd6a420acae65ad016df223b09c4a542cbc8c0ae61b4d7b391228a211cde973635ed49c47b1449791982f3b32d799319bb174f42
checksum: 22f89d505cc3d93020de38e4471362fbecd73a8df58017553ad57fb14e69b6f2f88bcdfe9f84b291442ed0654f97344d517af291d23848e43e6e208ee23dac2b
languageName: node
linkType: hard