misc: finalized login logic and other ui/ux changes

This commit is contained in:
Sheen Capadngan
2024-12-11 22:49:26 +08:00
parent b2efb2845a
commit 8d79fa3529
4 changed files with 55 additions and 9 deletions
@@ -37,7 +37,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
) )
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`) .leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`) .leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
.select(selectAllTableCols(TableName.IdentityAccessToken)) .select(selectAllTableCols(TableName.IdentityAccessToken))
.select( .select(
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"), db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
@@ -47,6 +47,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"), db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"), db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"), db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
db.ref("name").withSchema(TableName.Identity) db.ref("name").withSchema(TableName.Identity)
) )
.first(); .first();
@@ -61,7 +62,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure, trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s, trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc, trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt
}; };
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" }); throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
@@ -171,7 +171,8 @@ export const identityAccessTokenServiceFactory = ({
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth, [IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth, [IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth, [IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
[IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth [IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth,
[IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth
}; };
const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod]; const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod];
@@ -127,29 +127,47 @@ export const identityJwtAuthServiceFactory = ({
} }
if (identityJwtAuth.boundIssuer) { if (identityJwtAuth.boundIssuer) {
if (!tokenData.iss) {
throw new UnauthorizedError({
message: "Access denied: token has no issuer field"
});
}
if (!doesFieldValueMatchJwtPolicy(tokenData.iss, identityJwtAuth.boundIssuer)) { if (!doesFieldValueMatchJwtPolicy(tokenData.iss, identityJwtAuth.boundIssuer)) {
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
message: "Access denied: issuer mismatch." message: "Access denied: issuer mismatch"
}); });
} }
} }
if (identityJwtAuth.boundSubject) { if (identityJwtAuth.boundSubject) {
if (!tokenData.sub) {
throw new UnauthorizedError({
message: "Access denied: token has no subject field"
});
}
if (!doesFieldValueMatchJwtPolicy(tokenData.sub, identityJwtAuth.boundSubject)) { if (!doesFieldValueMatchJwtPolicy(tokenData.sub, identityJwtAuth.boundSubject)) {
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
message: "Access denied: subject not allowed." message: "Access denied: subject not allowed"
}); });
} }
} }
if (identityJwtAuth.boundAudiences) { if (identityJwtAuth.boundAudiences) {
if (!tokenData.aud) {
throw new UnauthorizedError({
message: "Access denied: token has no audience field"
});
}
if ( if (
!identityJwtAuth.boundAudiences !identityJwtAuth.boundAudiences
.split(", ") .split(", ")
.some((policyValue) => doesFieldValueMatchJwtPolicy(tokenData.aud, policyValue)) .some((policyValue) => doesFieldValueMatchJwtPolicy(tokenData.aud, policyValue))
) { ) {
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Access denied: audience not allowed." message: "Access denied: token audience not allowed"
}); });
} }
} }
@@ -157,12 +175,19 @@ export const identityJwtAuthServiceFactory = ({
if (identityJwtAuth.boundClaims) { if (identityJwtAuth.boundClaims) {
Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => { Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityJwtAuth.boundClaims as Record<string, string>)[claimKey]; const claimValue = (identityJwtAuth.boundClaims as Record<string, string>)[claimKey];
if (!tokenData[claimKey]) {
throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field`
});
}
// handle both single and multi-valued claims // handle both single and multi-valued claims
if ( if (
!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchJwtPolicy(tokenData[claimKey], claimEntry)) !claimValue.split(", ").some((claimEntry) => doesFieldValueMatchJwtPolicy(tokenData[claimKey], claimEntry))
) { ) {
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Access denied: claim mismatch." message: `Access denied: claim mismatch for field ${claimKey}`
}); });
} }
}); });
@@ -389,7 +414,7 @@ export const identityJwtAuthServiceFactory = ({
orgId: actorOrgId orgId: actorOrgId
}); });
if (jwksCaCert) { if (jwksCaCert !== undefined) {
const { cipherTextBlob: encryptedJwksCaCert } = orgDataKeyEncryptor({ const { cipherTextBlob: encryptedJwksCaCert } = orgDataKeyEncryptor({
plainText: Buffer.from(jwksCaCert) plainText: Buffer.from(jwksCaCert)
}); });
@@ -359,13 +359,31 @@ export const IdentityJwtAuthForm = ({
label={`Public Key ${index + 1}`} label={`Public Key ${index + 1}`}
errorText={error?.message} errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
icon={
<Tooltip
className="text-center"
content={<span>This field only accepts PEM-formatted public keys</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
> >
<TextArea {...field} placeholder="-----BEGIN PUBLIC KEY----- ..." /> <TextArea {...field} placeholder="-----BEGIN PUBLIC KEY----- ..." />
</FormControl> </FormControl>
)} )}
/> />
<IconButton <IconButton
onClick={() => removePublicKeyFields(index)} onClick={() => {
if (publicKeyFields.length === 1) {
createNotification({
type: "error",
text: "A public key is required for static configurations"
});
return;
}
removePublicKeyFields(index);
}}
size="lg" size="lg"
colorSchema="danger" colorSchema="danger"
variant="plain" variant="plain"