feat: added initial server sso key rotation semantics (#2696)

This commit is contained in:
Wyatt Johnson
2019-11-07 21:53:28 +00:00
committed by Kim Gardner
parent 59b8dfccda
commit 2dbba52fbd
14 changed files with 441 additions and 88 deletions
+43 -3
View File
@@ -1,6 +1,8 @@
import { Omit } from "coral-common/types";
import { Omit, RequireProperty } from "coral-common/types";
import {
GQLAuth,
GQLAuthenticationTargetFilter,
GQLEmailConfiguration,
GQLFacebookAuthIntegration,
GQLGoogleAuthIntegration,
@@ -9,7 +11,6 @@ import {
GQLMODERATION_MODE,
GQLOIDCAuthIntegration,
GQLSettings,
GQLSSOAuthIntegration,
} from "coral-server/graph/tenant/schema/__generated__/types";
export type LiveConfiguration = Omit<GQLLiveConfiguration, "configurable">;
@@ -37,13 +38,52 @@ export type FacebookAuthIntegration = Omit<
"callbackURL" | "redirectURL"
>;
export interface SSOKey {
/**
* kid is the identifier for the key used when verifying tokens issued by the
* provider.
*/
kid: string;
/**
* secret is the actual underlying secret used to verify the tokens with. When
* this is not available, it indicates that the token secret was deleted.
*/
secret?: string;
/**
* createdAt is the time that this key was created at.
*/
createdAt: Date;
/**
* deprecateAt when provided is the time that the token should no longer be
* valid at.
*/
deprecateAt?: Date;
/**
* deletedAt is the timestamp that the token was revoked.
*/
deletedAt?: Date;
}
export type RequiredSSOKey = RequireProperty<SSOKey, "secret">;
export interface SSOAuthIntegration {
enabled: boolean;
allowRegistration: boolean;
targetFilter: GQLAuthenticationTargetFilter;
keys: SSOKey[];
}
/**
* AuthIntegrations are the set of configurations for the variations of
* authentication solutions.
*/
export interface AuthIntegrations {
local: GQLLocalAuthIntegration;
sso: GQLSSOAuthIntegration;
sso: SSOAuthIntegration;
oidc: OIDCAuthIntegration;
google: GoogleAuthIntegration;
facebook: FacebookAuthIntegration;
+13 -2
View File
@@ -7,6 +7,8 @@ import {
} from "coral-server/graph/tenant/schema/__generated__/types";
import { translate } from "coral-server/services/i18n";
import { SSOKey } from "../settings";
export const getDefaultReactionConfiguration = (
bundle: FluentBundle
): GQLReactionConfiguration => ({
@@ -28,10 +30,19 @@ export const getDefaultStaffConfiguration = (
label: translate(bundle, "Staff", "staff-label"),
});
export function generateSSOKey() {
export function generateRandomString(size: number, drift = 5) {
return crypto
.randomBytes(size + Math.floor(Math.random() * drift))
.toString("hex");
}
export function generateSSOKey(createdAt: Date): SSOKey {
// Generate a new key. We generate a key of minimum length 32 up to 37 bytes,
// as 16 was the minimum length recommended.
//
// Reference: https://security.stackexchange.com/a/96176
return crypto.randomBytes(32 + Math.floor(Math.random() * 5)).toString("hex");
const secret = generateRandomString(32, 5);
const kid = generateRandomString(8, 3);
return { kid, secret, createdAt };
}
+34 -16
View File
@@ -125,8 +125,8 @@ export async function createTenant(
admin: true,
stream: true,
},
key: generateSSOKey(),
keyGeneratedAt: now,
// TODO: [CORL-754] (wyattjoh) remove this in favor of generating this when needed
keys: [generateSSOKey(now)],
},
oidc: {
enabled: false,
@@ -277,25 +277,17 @@ export async function updateTenant(
* for the specified Tenant. All existing user sessions signed with the old
* secret will be invalidated.
*/
export async function regenerateTenantSSOKey(mongo: Db, id: string) {
// Construct the update.
const update: DeepPartial<Tenant> = {
auth: {
integrations: {
sso: {
key: generateSSOKey(),
keyGeneratedAt: new Date(),
},
},
},
};
export async function createTenantSSOKey(mongo: Db, id: string, now: Date) {
// Construct the new key.
const key = generateSSOKey(now);
// Update the Tenant with this new key.
const result = await collection(mongo).findOneAndUpdate(
{ id },
// Serialize the deep update into the Tenant.
{
$set: dotize(update),
$push: {
"auth.integrations.sso.keys": key,
},
},
// False to return the updated document instead of the original
// document.
@@ -304,3 +296,29 @@ export async function regenerateTenantSSOKey(mongo: Db, id: string) {
return result.value || null;
}
export async function deprecateTenantSSOKey(
mongo: Db,
id: string,
kid: string,
deprecateAt: Date
) {
// Update the tenant.
const result = await collection(mongo).findOneAndUpdate(
{ id },
{
$set: {
"auth.integrations.sso.keys.$[keys].deprecateAt": deprecateAt,
},
},
{
// False to return the updated document instead of the original
// document.
returnOriginal: false,
// Add an ArrayFilter to only update one of the keys.
arrayFilters: [{ "keys.kid": kid }],
}
);
return result.value || null;
}