Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
67af202
Fix/silence lint issues
nicholasmarais1158 Apr 17, 2025
3645dd7
Fix spacing on side panel
nicholasmarais1158 Apr 17, 2025
f197c29
Return github-specific config from `GET /webapi/tokens`
nicholasmarais1158 Apr 23, 2025
c7805d2
Add github form for create/edit
nicholasmarais1158 Apr 24, 2025
bb2bf9a
Handle unsupported fields during edit
nicholasmarais1158 Apr 25, 2025
1ad2db8
Add js docs for check functions
nicholasmarais1158 Apr 25, 2025
8d0d848
Add js docs for github check function
nicholasmarais1158 Apr 25, 2025
0353e4b
Fix casing in test mocks
nicholasmarais1158 Apr 25, 2025
54fa824
Fix missing `readonly` prop
nicholasmarais1158 Apr 25, 2025
99e97b3
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens
nicholasmarais1158 Apr 25, 2025
c2a4dbb
Remove unused import
nicholasmarais1158 Apr 25, 2025
2277a4e
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens
nicholasmarais1158 Apr 28, 2025
26ebb15
Retain metadata (inc expiry) on token edit
nicholasmarais1158 Apr 28, 2025
93a2247
Remove mutual-exclusivity on repo/owner fields
nicholasmarais1158 Apr 28, 2025
c2d0292
Improve supported fields readability
nicholasmarais1158 Apr 28, 2025
e1f827b
Hide GHES config
nicholasmarais1158 Apr 28, 2025
6eb7de0
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens
nicholasmarais1158 Apr 28, 2025
e8232a8
Ignore token not found errors when creating
nicholasmarais1158 Apr 28, 2025
5234b8a
Lock GHES fields when using OSS
nicholasmarais1158 Apr 28, 2025
f7865f2
Lint fix
nicholasmarais1158 Apr 29, 2025
984a1fa
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens
nicholasmarais1158 Apr 29, 2025
05de19a
Return github-specific config from `GET /webapi/tokens`
nicholasmarais1158 Apr 23, 2025
0943a83
Support "token" in `/webapi/yaml/parse/:kind`
nicholasmarais1158 Apr 25, 2025
413cde5
Use empty time.Time for token expiry (`POST /webapi/tokens`)
nicholasmarais1158 Apr 28, 2025
a751430
Merge branch 'nickmarais/feat/44794-github-join-tokens-api' into nick…
nicholasmarais1158 Apr 29, 2025
f985715
Revert createTokenForDiscoveryHandle changes
nicholasmarais1158 Apr 29, 2025
928c83e
Fix acronym casing
nicholasmarais1158 Apr 29, 2025
2b90f08
Cover enterprise token types in tests
nicholasmarais1158 Apr 30, 2025
e7fd9a5
Cover github tokens in existing tests
nicholasmarais1158 Apr 30, 2025
98c1c72
Tweak handling of `tokenId`
nicholasmarais1158 Apr 30, 2025
1311d91
Check expiry is not overwritten
nicholasmarais1158 Apr 30, 2025
7696729
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens-api
nicholasmarais1158 Apr 30, 2025
f0254a6
Revert removing tokenId check
nicholasmarais1158 Apr 30, 2025
0209744
Merge branch 'nickmarais/feat/44794-github-join-tokens-api' into nick…
nicholasmarais1158 Apr 30, 2025
521b629
Refactor supported fields
nicholasmarais1158 Apr 30, 2025
c4aa6ec
Merge branch 'master' into nickmarais/feat/44794-github-join-tokens
nicholasmarais1158 May 1, 2025
0c27883
Remove use of `X-Teleport-TokenName`
nicholasmarais1158 May 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions web/packages/design/src/utils/testing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
render as testingRender,
waitFor,
waitForElementToBeRemoved,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ReactNode } from 'react';
Expand Down Expand Up @@ -78,8 +79,8 @@ screen.debug = () => {
};

type RenderOptions = {
wrapper: React.FC;
container: HTMLElement;
wrapper?: React.FC;
container?: HTMLElement;
};

export {
Expand All @@ -95,4 +96,5 @@ export {
Router,
userEvent,
waitForElementToBeRemoved,
within,
};
97 changes: 97 additions & 0 deletions web/packages/shared/utils/collectKeys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { collectKeys } from './collectKeys';

describe('collectKeys', () => {
it.each`
value
${undefined}
${null}
${1}
${true}
${() => {}}
`('supports non object values ($value)', ({ value }) => {
const actual = collectKeys(value);
expect(actual).toBeNull();
});

it('supports empty object values', () => {
const actual = collectKeys({});
expect(actual).toEqual([]);
});

it('supports empty array values', () => {
const actual = collectKeys([]);
expect(actual).toEqual([]);
});

it('supports simple object values', () => {
const actual = collectKeys({
number: 1,
boolean: true,
string: 'string',
function: () => {},
null: null,
undefined: undefined,
});
expect(actual).toEqual([
'.number',
'.boolean',
'.string',
'.function',
'.null',
'.undefined',
]);
});

it('supports simple array values', () => {
const actual = collectKeys([
{ alpha: true },
{ alpha: true },
{ beta: true },
]);
expect(actual).toEqual(['.alpha', '.alpha', '.beta']);
});

it('supports nested object values', () => {
const actual = collectKeys([
{
inner: {
foo: 'bar',
},
},
]);
expect(actual).toEqual(['.inner.foo']);
});

it('supports nested array values', () => {
const actual = collectKeys([[{ foo: 'bar' }], { bar: 'foo' }]);
expect(actual).toEqual(['.foo', '.bar']);
});

it('allows a custom key prefix', () => {
const actual = collectKeys(
{
foo: 1,
},
'root'
);
expect(actual).toEqual(['root.foo']);
});
});
42 changes: 42 additions & 0 deletions web/packages/shared/utils/collectKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* `collectKeys` gathers object keys recursively and returns them. Arrays are
* traversed, but are transparent. Returns null if the value is not an object
* or array of objects, and an empty array of no keys are present.
*
* @param value Value from which keys will be collected
* @param prefix An optional value to be prepended to all keys returned
* @returns An array of the keys collected (if any) or null
*/
export const collectKeys = (value: unknown, prefix: string = '') => {
if (typeof value !== 'object' || value === null) {
return prefix ? [prefix] : null;
}

if (Array.isArray(value)) {
return value.flatMap(val => {
return collectKeys(val, prefix);
});
}

return Object.entries(value).flatMap(([k, v]) => {
return collectKeys(v, `${prefix}.${k}`);
});
};
14 changes: 14 additions & 0 deletions web/packages/teleport/src/JoinTokens/JoinTokenForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import {
export const JoinTokenIAMForm = ({
tokenState,
onUpdateState,
readonly,
}: {
tokenState: NewJoinTokenState;
onUpdateState: (newToken: NewJoinTokenState) => void;
readonly: boolean;
}) => {
const rules = tokenState.iam;

Expand Down Expand Up @@ -104,13 +106,15 @@ export const JoinTokenIAMForm = ({
onChange={e =>
setTokenRulesField(index, 'aws_account', e.target.value)
}
readonly={readonly}
/>
<FieldInput
label="ARN"
toolTipContent={`The joining nodes must match this ARN. Supports wildcards "*" and "?"`}
placeholder="arn:aws:iam::account-id:role/*"
value={rule.aws_arn}
onChange={e => setTokenRulesField(index, 'aws_arn', e.target.value)}
readonly={readonly}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would readonly be true for IAM forms?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it's data includes configuration other than .aws_account and .aws_arn. In which case editing the fields is prevented for all join methods and the submit button is disabled.

Does that behaviour feel ok?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sounds good!

/>
</RuleBox>
))}
Expand All @@ -125,9 +129,11 @@ export const JoinTokenIAMForm = ({
export const JoinTokenGCPForm = ({
tokenState,
onUpdateState,
readonly,
}: {
tokenState: NewJoinTokenState;
onUpdateState: (newToken: NewJoinTokenState) => void;
readonly: boolean;
}) => {
const rules = tokenState.gcp;
function removeRule(index: number) {
Expand Down Expand Up @@ -198,6 +204,7 @@ export const JoinTokenGCPForm = ({
value={rule.project_ids}
label="Add Project ID(s)"
rule={requiredField('At least 1 Project ID required')}
isDisabled={readonly}
/>
<FieldSelectCreatable
placeholder="us-west1, us-east1-a"
Expand All @@ -210,6 +217,7 @@ export const JoinTokenGCPForm = ({
value={rule.locations}
label="Add Locations"
helperText="Allows regions and/or zones."
isDisabled={readonly}
/>
<FieldSelectCreatable
placeholder="PROJECT_compute@developer.gserviceaccount.com"
Expand All @@ -221,6 +229,7 @@ export const JoinTokenGCPForm = ({
}
value={rule.service_accounts}
label="Add Service Account Emails"
isDisabled={readonly}
/>
</RuleBox>
))}
Expand All @@ -235,9 +244,11 @@ export const JoinTokenGCPForm = ({
export const JoinTokenOracleForm = ({
tokenState,
onUpdateState,
readonly,
}: {
tokenState: NewJoinTokenState;
onUpdateState: (newToken: NewJoinTokenState) => void;
readonly: boolean;
}) => {
const rules = tokenState.oracle;
function removeRule(index: number) {
Expand Down Expand Up @@ -301,6 +312,7 @@ export const JoinTokenOracleForm = ({
placeholder="ocid1.tenancy.oc1..<unique ID>"
value={rule.tenancy}
onChange={e => updateRuleField(index, 'tenancy', e.target.value)}
readonly={readonly}
/>
<FieldSelectCreatable
placeholder="ocid1.compartment.oc1..<unique ID>"
Expand All @@ -317,6 +329,7 @@ export const JoinTokenOracleForm = ({
value={rule.parent_compartments}
label="Add Compartments"
helperText="Direct parent compartments only, no nested compartments."
isDisabled={readonly}
/>
<FieldSelectCreatable
placeholder="us-ashburn-1, phx"
Expand All @@ -328,6 +341,7 @@ export const JoinTokenOracleForm = ({
}
value={rule.regions}
label="Add Regions"
isDisabled={readonly}
/>
</RuleBox>
))}
Expand Down
Loading
Loading