[next] Tasks (#1777)

* feat: initial support for synced tenants

* fix: cleanup

* fix: logger now respects logging level

* fix: cache now ignores updates issued from itself

* feat: print subscriber count

* feat: initial moderation + validation for new comments

* fix: added Promiseable type

* feat: initial actions impl

* feat: more moderation phases

* fix: handle settings inheritence

* fix: moved settings into new file

* fix: defaults and documentation

* fix: replace merge with object spread

* feat: added integration with akismet

* fix: support tenant cache for oidc strategy

* fix: fixed compile

* fix: import ordering

* feat: added bull for queue support

* feat: support for scraping

* fix: fixes for scraper

- Implemented simple metascraper replacement (to resolve security advisory
  warning)
- Implemented simle dotize replacement (to resolve not
  working version that couldn't handle date objects)
- Plugged in asset scraping to asset creation process

* fix: handles array values

* feat: added initial scraper implementation

* feat: seperate queues but share config

* fix: simplified auth data access

* feat: moved more settings into the graph

* feat: improved mailer design

* fix: fixed issue with dotize

* fix: fixed some issues with adapter

* fix: queue cleanup

* feat: added organizationName to Tenant

* feat: email rendering

* review: support es6 imports

* fix: restore old ci step

* fix: adjusted logging messages
This commit is contained in:
Wyatt Johnson
2018-09-04 18:47:20 +00:00
committed by GitHub
parent 76d198f2a6
commit 59cf728681
46 changed files with 1804 additions and 377 deletions
+15 -10
View File
@@ -141,19 +141,18 @@ export function createJWTSigningConfig(config: Config): JWTSigningConfig {
export type SigningTokenOptions = Pick<SignOptions, "audience" | "issuer">;
export async function signTokenString(
export const signTokenString = async (
{ algorithm, secret }: JWTSigningConfig,
user: User,
options: SigningTokenOptions
) {
return jwt.sign({}, secret, {
) =>
jwt.sign({}, secret, {
...options,
jwtid: uuid.v4(),
algorithm,
expiresIn: "1 day", // TODO: (wyattjoh) evaluate allowing configuration?
subject: user.id,
});
}
export interface JWTToken {
jti: string;
@@ -208,17 +207,23 @@ export class JWTStrategy extends Strategy {
// Use the algorithm specified in the configuration.
algorithms: [this.signingConfig.algorithm],
},
async (err: Error | undefined, { jti, sub }: JWTToken) => {
async (err: Error | undefined, decoded: JWTToken) => {
if (err) {
return this.fail(err, 401);
}
try {
// Check to see if the token has been blacklisted.
await checkBlacklistJWT(this.redis, jti);
if (!decoded) {
// There was no token on the request, so there was no user, so let's
// mark that the strategy was successful.
return this.success(null, null);
}
// Find the user referenced by the token.
const user = await retrieveUser(this.mongo, tenant.id, sub);
try {
// Find the user.
const user = await retrieveUser(this.mongo, tenant.id, decoded.sub);
// Check to see if the token has been blacklisted.
await checkBlacklistJWT(this.redis, decoded.jti);
// Return them! The user may be null, but that's ok here.
this.success(user, null);
+27 -11
View File
@@ -5,11 +5,12 @@ import { Db } from "mongodb";
import { Strategy as OAuth2Strategy, VerifyCallback } from "passport-oauth2";
import { Strategy } from "passport-strategy";
import { Config } from "talk-common/config";
import { validate } from "talk-server/app/request/body";
import { reconstructURL } from "talk-server/app/url";
import { GQLUSER_ROLE } from "talk-server/graph/tenant/schema/__generated__/types";
import { OIDCAuthIntegration } from "talk-server/models/settings";
import {
GQLOIDCAuthIntegration,
GQLUSER_ROLE,
} from "talk-server/graph/tenant/schema/__generated__/types";
import { Tenant } from "talk-server/models/tenant";
import { OIDCProfile, retrieveUserWithProfile } from "talk-server/models/user";
import TenantCache from "talk-server/services/tenant/cache";
@@ -83,7 +84,9 @@ const signingKeyFactory = (client: jwks.JwksClient): jwt.KeyFunction => (
});
};
function getEnabledIntegration(tenant: Tenant) {
function getEnabledIntegration(
tenant: Tenant
): Required<GQLOIDCAuthIntegration> {
const integration = tenant.auth.integrations.oidc;
if (!integration) {
// TODO: return a better error.
@@ -96,7 +99,21 @@ function getEnabledIntegration(tenant: Tenant) {
throw new Error("integration not enabled");
}
return integration;
if (
!integration.name ||
!integration.clientID ||
!integration.clientSecret ||
!integration.authorizationURL ||
!integration.tokenURL ||
!integration.jwksURI ||
!integration.issuer
) {
// TODO: return a better error.
throw new Error("integration not configured");
}
// TODO: (wyattjoh) for some reason, type guards above to not allow coercion to this required type.
return integration as Required<GQLOIDCAuthIntegration>;
}
export const OIDCIDTokenSchema = Joi.object()
@@ -179,7 +196,6 @@ const OIDC_SCOPE = "openid email profile";
export interface OIDCStrategyOptions {
mongo: Db;
tenantCache: TenantCache;
config: Config;
}
export default class OIDCStrategy extends Strategy {
@@ -188,11 +204,11 @@ export default class OIDCStrategy extends Strategy {
private mongo: Db;
private cache: TenantCacheAdapter<StrategyItem>;
constructor({ mongo, tenantCache, config }: OIDCStrategyOptions) {
constructor({ mongo, tenantCache }: OIDCStrategyOptions) {
super();
this.mongo = mongo;
this.cache = new TenantCacheAdapter(tenantCache, config);
this.cache = new TenantCacheAdapter(tenantCache);
// Connect the cache adapter.
this.cache.subscribe();
@@ -201,7 +217,7 @@ export default class OIDCStrategy extends Strategy {
private lookupJWKSClient(
req: Request,
tenantID: string,
oidc: OIDCAuthIntegration
oidc: Required<GQLOIDCAuthIntegration>
) {
let entry = this.cache.get(tenantID);
if (!entry) {
@@ -257,7 +273,7 @@ export default class OIDCStrategy extends Strategy {
// Get the integration from the tenant. If needed, it will be used to create
// a new strategy.
let integration: OIDCAuthIntegration;
let integration: Required<GQLOIDCAuthIntegration>;
try {
integration = getEnabledIntegration(tenant);
} catch (err) {
@@ -297,7 +313,7 @@ export default class OIDCStrategy extends Strategy {
private createStrategy(
req: Request,
integration: OIDCAuthIntegration
integration: Required<GQLOIDCAuthIntegration>
): OAuth2Strategy {
const { clientID, clientSecret, authorizationURL, tokenURL } = integration;