Skip to content

Commit

Permalink
Merge pull request #16 from abjrcode/proper-ids
Browse files Browse the repository at this point in the history
use ksids for unique identifiers and add label field for aws iam idc
  • Loading branch information
abjrcode authored Nov 23, 2023
2 parents a9c4d7d + 1e0a9d1 commit 2b229ef
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 267 deletions.
7 changes: 3 additions & 4 deletions clients/awssso/aws-sso.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package awssso
import (
"context"
"errors"
"net/url"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sso"
Expand Down Expand Up @@ -74,7 +73,7 @@ type ListAccountsResponse struct {
type AwsSsoOidcClient interface {
RegisterClient(ctx context.Context, friendlyClientName string) (*RegistrationResponse, error)

StartDeviceAuthorization(ctx context.Context, startUrl url.URL, clientId, clientSecret string) (*AuthorizationResponse, error)
StartDeviceAuthorization(ctx context.Context, startUrl string, clientId, clientSecret string) (*AuthorizationResponse, error)

CreateToken(ctx context.Context, clientId, clientSecret, userCode, deviceCode string) (*GetTokenResponse, error)

Expand Down Expand Up @@ -113,11 +112,11 @@ func (c *awsSsoClientImpl) RegisterClient(ctx context.Context, friendlyClientNam
}, nil
}

func (c *awsSsoClientImpl) StartDeviceAuthorization(ctx context.Context, startUrl url.URL, clientId, clientSecret string) (*AuthorizationResponse, error) {
func (c *awsSsoClientImpl) StartDeviceAuthorization(ctx context.Context, startUrl string, clientId, clientSecret string) (*AuthorizationResponse, error) {
output, err := c.oidcClient.StartDeviceAuthorization(ctx, &ssooidc.StartDeviceAuthorizationInput{
ClientId: aws.String(clientId),
ClientSecret: aws.String(clientSecret),
StartUrl: aws.String(startUrl.String()),
StartUrl: aws.String(startUrl),
}, func(options *ssooidc.Options) {
options.Region = ctx.Value(AwsRegion("awsRegion")).(string)
})
Expand Down
29 changes: 15 additions & 14 deletions dashboard-controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ type Provider struct {
IconSvgBase64 string `json:"iconSvgBase64"`
}

type ConfiguredProvider struct {
Code string `json:"code"`
DisplayName string `json:"displayName"`
InstanceId string `json:"instanceId"`
IsFavorite bool `json:"isFavorite"`
IconSvgBase64 string `json:"iconSvgBase64"`
type FavoriteInstance struct {
ProviderCode string `json:"providerCode"`
InstanceId string `json:"instanceId"`
}

var (
Expand Down Expand Up @@ -59,25 +56,29 @@ func (c *DashboardController) Init(ctx context.Context, errorHandler logging.Err
}
}

func (c *DashboardController) ListFavorites() ([]ConfiguredProvider, error) {
rows, err := c.db.QueryContext(c.ctx, `SELECT * FROM providers WHERE is_favorite = ?;`, true)
func (c *DashboardController) ListFavorites() ([]FavoriteInstance, error) {
rows, err := c.db.QueryContext(c.ctx, `SELECT * FROM favorite_instances`)

if err != nil {
return []ConfiguredProvider{}, err
if err == sql.ErrNoRows {
return []FavoriteInstance{}, nil
}

c.errorHandler.Catch(c.logger, err)
}

providers := make([]ConfiguredProvider, 0, 10)
favorites := make([]FavoriteInstance, 0, 10)

for rows.Next() {
var provider ConfiguredProvider
err := rows.Scan(&provider.Code, &provider.InstanceId, &provider.DisplayName, &provider.IsFavorite)
var favorite FavoriteInstance
err := rows.Scan(&favorite.ProviderCode, &favorite.InstanceId)
if err != nil {
c.errorHandler.Catch(c.logger, err)
}
providers = append(providers, provider)
favorites = append(favorites, favorite)
}

return providers, nil
return favorites, nil
}

func (c *DashboardController) ListProviders() []Provider {
Expand Down
118 changes: 57 additions & 61 deletions frontend/src/routes/aws-iam-idc/aws-iam-idc-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ import {
AwsIamIdcCardDataResult,
} from "./aws-iam-idc-card-data"

export function AwsIamIdcCard({
instanceId,
displayName,
}: {
instanceId: string
displayName: string
}) {
export function AwsIamIdcCard({ instanceId }: { instanceId: string }) {
const navigate = useNavigate()
const fetcher = useFetcher()

Expand All @@ -23,9 +17,11 @@ export function AwsIamIdcCard({
navigate("/providers/aws-iam-idc/device-auth", {
state: {
action: "refresh",
instanceId: deviceAuthFlowResult.instanceId,
clientId: deviceAuthFlowResult.clientId,
startUrl: deviceAuthFlowResult.startUrl,
awsRegion: deviceAuthFlowResult.region,
label: deviceAuthFlowResult.label,
verificationUriComplete: deviceAuthFlowResult.verificationUri,
userCode: deviceAuthFlowResult.userCode,
deviceCode: deviceAuthFlowResult.deviceCode,
Expand Down Expand Up @@ -55,44 +51,66 @@ export function AwsIamIdcCard({
<div
role="heading"
className="card-title justify-between">
<h1 className="text-2xl font-semibold">{displayName}</h1>
<h1 className="text-2xl font-semibold">{cardData.label}</h1>
</div>
<div className="card-body">
<h2 className="text-xl">Accounts</h2>
<ul className="list-disc pl-4 space-y-4">
{cardData.accounts.map((account) => (
<li key={account.accountId}>
<h3 className="text-lg">
{account.accountName} ({account.accountId})
</h3>
<ul className="list-inside space-y-2 list-disc pl-4">
<li>
<span>Admin</span>
<div className="inline-flex gap-2">
<button className="btn btn-secondary btn-outline btn-xs">
copy credentials
</button>
<a className="link link-secondary"> Management console </a>
</div>
{!cardData.isAccessTokenExpired && (
<>
<h2 className="text-xl">Accounts</h2>
<ul className="list-disc pl-4 space-y-4">
{cardData.accounts.map((account) => (
<li key={account.accountId}>
<h3 className="text-lg">
{account.accountName} ({account.accountId})
</h3>
<ul className="list-inside space-y-2 list-disc pl-4">
<li>
<span>Admin</span>
<div className="inline-flex gap-2">
<button className="btn btn-secondary btn-outline btn-xs">
copy credentials
</button>
<a className="link link-secondary"> Management console </a>
</div>
</li>
<li>
<span>Viewer</span>
<div className="inline-flex gap-2">
<button className="btn btn-secondary btn-outline btn-xs">
copy credentials
</button>
<a className="link link-secondary"> Management console </a>
</div>
</li>
</ul>
</li>
<li>
<span>Viewer</span>
<div className="inline-flex gap-2">
<button className="btn btn-secondary btn-outline btn-xs">
copy credentials
</button>
<a className="link link-secondary"> Management console </a>
</div>
</li>
</ul>
</li>
))}
</ul>
))}
</ul>{" "}
</>
)}

{cardData.isAccessTokenExpired && (
<>
<h2 className="text-xl">Access Token Has Expired</h2>
<p>Please renew it by authorizing the device again.</p>
</>
)}
</div>
<div className="card-actions items-center justify-between">
{cardData.isAccessTokenExpired && (
<div className="flex flex-col gap-2">
<button
className="btn btn-primary"
onClick={() => authorizeDevice(instanceId)}>
Renew
</button>
</div>
)}
<div className="flex flex-col gap-2">
<p className="badge badge-outline">
Expires In: {cardData.accessTokenExpiresIn}
{!cardData.isAccessTokenExpired
? `Expires in ${cardData.accessTokenExpiresIn}`
: `Token expired ${cardData.accessTokenExpiresIn}`}
</p>
</div>
</div>
Expand All @@ -101,35 +119,13 @@ export function AwsIamIdcCard({
}

switch (cardDataResult.code) {
case AwsIamIdcCardDataError.ErrAccessTokenExpired:
return (
<div className="card gap-6 px-6 py-4 card-bordered border-secondary bg-base-200 drop-shadow-lg">
<div
role="heading"
className="card-title justify-between">
<h1 className="text-2xl font-semibold">{displayName}</h1>
</div>
<div className="card-body">
<h2 className="text-xl">Access Token Expired</h2>
</div>
<div className="card-actions justify-between">
<div className="flex items-center gap-4">
<button
className="btn btn-primary"
onClick={async () => await authorizeDevice(instanceId)}>
Get new token
</button>
</div>
</div>
</div>
)
case AwsIamIdcCardDataError.ErrTransientAwsClientError:
return (
<div className="card gap-6 px-6 py-4 card-bordered border-secondary bg-base-200 drop-shadow-lg">
<div
role="heading"
className="card-title justify-between">
<h1 className="text-2xl font-semibold">{displayName}</h1>
<h1 className="text-2xl font-semibold">AWS IAM Identity Center</h1>
</div>
<div className="card-body">
<h2 className="text-xl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum AwsIamIdcDeviceAuthFlowError {
ErrDeviceAuthFlowTimedOut = "DEVICE_AUTH_FLOW_TIMED_OUT",
ErrInvalidStartUrl = "INVALID_START_URL",
ErrInvalidAwsRegion = "INVALID_AWS_REGION",
ErrInvalidLabel = "INVALID_LABEL",
ErrTransientAwsClientError = "TRANSIENT_AWS_CLIENT_ERROR",
}

Expand All @@ -17,19 +18,21 @@ export async function awsIamIdcDeviceAuthAction({ request }: ActionFunctionArgs)
const updates = Object.fromEntries(formData)

const action = updates["action"].toString()
const instanceId = updates["instanceId"].toString()
const clientId = updates["clientId"].toString()
const startUrl = updates["startUrl"].toString()
const awsRegion = updates["awsRegion"].toString()
const label = updates["label"].toString()
const userCode = updates["userCode"].toString()
const deviceCode = updates["deviceCode"].toString()

try {
switch (action) {
case "setup":
await FinalizeSetup(clientId, startUrl, awsRegion, userCode, deviceCode)
await FinalizeSetup(clientId, startUrl, awsRegion, label, userCode, deviceCode)
break;
case "refresh":
await FinalizeRefreshAccessToken(clientId, startUrl, awsRegion, userCode, deviceCode)
await FinalizeRefreshAccessToken(instanceId, awsRegion, userCode, deviceCode)
break;
default:
throw new Error(`Unknown action: ${action}`)
Expand All @@ -44,6 +47,8 @@ export async function awsIamIdcDeviceAuthAction({ request }: ActionFunctionArgs)
return { success: false, code: AwsIamIdcDeviceAuthFlowError.ErrInvalidStartUrl, error: e }
case AwsIamIdcDeviceAuthFlowError.ErrInvalidAwsRegion:
return { success: false, code: AwsIamIdcDeviceAuthFlowError.ErrInvalidAwsRegion, error: e }
case AwsIamIdcDeviceAuthFlowError.ErrInvalidLabel:
return { success: false, code: AwsIamIdcDeviceAuthFlowError.ErrInvalidLabel, error: e }
case AwsIamIdcDeviceAuthFlowError.ErrTransientAwsClientError:
return { success: false, code: AwsIamIdcDeviceAuthFlowError.ErrTransientAwsClientError, error: e }
default:
Expand Down
31 changes: 23 additions & 8 deletions frontend/src/routes/aws-iam-idc/aws-iam-idc-device-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import {
AwsIamIdcDeviceAuthFlowResult,
} from "./aws-iam-idc-device-auth-data"
import { useEffect, useRef } from "react"
import { useWails } from "../../wails-provider/wails-context"
import { useToaster } from "../../toast-provider/toast-context"

export function AwsIamIdcDeviceAuth() {
const toaster = useToaster()
const wails = useWails()
const location = useLocation()
const navigate = useNavigate()
const actionData = useActionData() as AwsIamIdcDeviceAuthFlowResult | undefined
Expand All @@ -27,10 +25,12 @@ export function AwsIamIdcDeviceAuth() {

const {
action,
verificationUriComplete,
instanceId,
clientId,
startUrl,
label,
awsRegion,
verificationUriComplete,
userCode,
deviceCode,
} = authFlowState.current
Expand All @@ -47,20 +47,25 @@ export function AwsIamIdcDeviceAuth() {
if (actionData.success === false) {
switch (actionData.code) {
case AwsIamIdcDeviceAuthFlowError.ErrDeviceAuthFlowNotAuthorized:
wails.runtime.ShowWarningDialog(
toaster.showError(
"You haven not authorized the device through the activation link :(\nPlease do so then click this button again",
)
return
case AwsIamIdcDeviceAuthFlowError.ErrDeviceAuthFlowTimedOut:
wails.runtime.ShowWarningDialog(
toaster.showError(
"The device authorization flow timed out and we have to start over",
)
return navigate("/")
case AwsIamIdcDeviceAuthFlowError.ErrInvalidStartUrl:
wails.runtime.ShowWarningDialog("The Start URL is not valid")
toaster.showError(
"The Start URL is not a valid AWS IAM Identity Center URL",
)
return
case AwsIamIdcDeviceAuthFlowError.ErrInvalidAwsRegion:
wails.runtime.ShowWarningDialog("The AWS region is not valid")
toaster.showError("The AWS region is not valid")
return
case AwsIamIdcDeviceAuthFlowError.ErrInvalidLabel:
toaster.showError("The account label must be between 1 and 50 characters")
return
case AwsIamIdcDeviceAuthFlowError.ErrTransientAwsClientError:
toaster.showWarning(
Expand All @@ -69,7 +74,7 @@ export function AwsIamIdcDeviceAuth() {
return
}
}
}, [toaster, wails, navigate, actionData])
}, [toaster, navigate, actionData])

return (
<Form
Expand All @@ -88,6 +93,11 @@ export function AwsIamIdcDeviceAuth() {
name="action"
value={action}
/>
<input
type="hidden"
name="instanceId"
value={instanceId}
/>
<input
type="hidden"
name="clientId"
Expand All @@ -103,6 +113,11 @@ export function AwsIamIdcDeviceAuth() {
name="awsRegion"
value={awsRegion}
/>
<input
type="hidden"
name="label"
value={label}
/>
<input
type="hidden"
name="userCode"
Expand Down
Loading

0 comments on commit 2b229ef

Please sign in to comment.