mirror of
https://github.com/wassname/talk.git
synced 2026-09-12 13:01:11 +08:00
feat: initial passport user auth impl
This commit is contained in:
@@ -3,6 +3,7 @@ import { Db } from "mongodb";
|
||||
import passport, { Authenticator } from "passport";
|
||||
|
||||
import {
|
||||
createJWTStrategy,
|
||||
JWTSigningConfig,
|
||||
SigningTokenOptions,
|
||||
signTokenString,
|
||||
@@ -20,10 +21,12 @@ export type VerifyCallback = (
|
||||
|
||||
export interface PassportOptions {
|
||||
db: Db;
|
||||
signingConfig: JWTSigningConfig;
|
||||
}
|
||||
|
||||
export function createPassport({
|
||||
db,
|
||||
signingConfig,
|
||||
}: PassportOptions): passport.Authenticator {
|
||||
// Create the authenticator.
|
||||
const auth = new Authenticator();
|
||||
@@ -34,6 +37,9 @@ export function createPassport({
|
||||
// Use the LocalStrategy.
|
||||
auth.use(createLocalStrategy({ db }));
|
||||
|
||||
// Use the JWTStrategy.
|
||||
auth.use(createJWTStrategy({ db, signingConfig }));
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
@@ -74,7 +80,7 @@ export async function handleSuccessfulLogin(
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate will wrap a authenticators authenticate method with one that
|
||||
* wrapAuthz will wrap a authenticators authenticate method with one that
|
||||
* will return a valid login token for a valid login by a compatible strategy.
|
||||
*
|
||||
* @param authenticator the base authenticator instance
|
||||
@@ -82,7 +88,7 @@ export async function handleSuccessfulLogin(
|
||||
* @param name the name of the authenticator to use
|
||||
* @param options any options to be passed to the authenticate call
|
||||
*/
|
||||
export const authenticate = (
|
||||
export const wrapAuthz = (
|
||||
authenticator: passport.Authenticator,
|
||||
signingConfig: JWTSigningConfig,
|
||||
name: string,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import jwt, { SignOptions } from "jsonwebtoken";
|
||||
import uuid from "uuid";
|
||||
|
||||
import { Db } from "mongodb";
|
||||
import { Strategy } from "passport-strategy";
|
||||
import { Config } from "talk-server/config";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { retrieveUser, User } from "talk-server/models/user";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
const authHeaderRegex = /(\S+)\s+(\S+)/;
|
||||
@@ -126,3 +128,76 @@ export async function signTokenString(
|
||||
subject: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
export interface JWTToken {
|
||||
jti: string;
|
||||
sub: string;
|
||||
exp: number;
|
||||
iss?: string;
|
||||
}
|
||||
|
||||
export interface JWTStrategyOptions {
|
||||
signingConfig: JWTSigningConfig;
|
||||
db: Db;
|
||||
}
|
||||
|
||||
export class JWTStrategy extends Strategy {
|
||||
private signingConfig: JWTSigningConfig;
|
||||
private db: Db;
|
||||
|
||||
public name: string;
|
||||
|
||||
constructor({ signingConfig, db }: JWTStrategyOptions) {
|
||||
super();
|
||||
|
||||
this.name = "jwt";
|
||||
this.signingConfig = signingConfig;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public authenticate(req: Request) {
|
||||
const { tenant } = req;
|
||||
if (!tenant) {
|
||||
// TODO: (wyattjoh) return a better error.
|
||||
return this.error(new Error("tenant not found"));
|
||||
}
|
||||
|
||||
// Lookup the token.
|
||||
const token = extractJWTFromRequest(req);
|
||||
if (!token) {
|
||||
// TODO: (wyattjoh) return a better error.
|
||||
return this.fail(new Error("no token on request"), 401);
|
||||
}
|
||||
|
||||
jwt.verify(
|
||||
token,
|
||||
// Use the secret specified in the configuration.
|
||||
this.signingConfig.secret,
|
||||
{
|
||||
// We need to verify that the token is for the specified tenant.
|
||||
issuer: tenant.id,
|
||||
// Use the algorithm specified in the configuration.
|
||||
algorithms: [this.signingConfig.algorithm],
|
||||
},
|
||||
async (err: Error | undefined, { sub }: JWTToken) => {
|
||||
if (err) {
|
||||
return this.fail(err, 401);
|
||||
}
|
||||
|
||||
try {
|
||||
// Find the user.
|
||||
const user = await retrieveUser(this.db, tenant.id, sub);
|
||||
|
||||
// Return them! The user may be null, but that's ok here.
|
||||
this.success(user, null);
|
||||
} catch (err) {
|
||||
return this.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function createJWTStrategy(options: JWTStrategyOptions) {
|
||||
return new JWTStrategy(options);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export default class SSOStrategy extends Strategy {
|
||||
*/
|
||||
private wrapNewTokenHandler = (tenant: Tenant) => async (
|
||||
err: Error | undefined,
|
||||
decoded: OIDCIDToken | SSOToken
|
||||
token: OIDCIDToken | SSOToken
|
||||
) => {
|
||||
if (err) {
|
||||
return this.fail(err, 401);
|
||||
@@ -190,7 +190,7 @@ export default class SSOStrategy extends Strategy {
|
||||
|
||||
try {
|
||||
// Find or create the user based on the decoded token.
|
||||
const user = await this.findOrCreateUser(tenant, decoded);
|
||||
const user = await this.findOrCreateUser(tenant, token);
|
||||
|
||||
// The user was found or created!
|
||||
return this.success(user, null);
|
||||
|
||||
Reference in New Issue
Block a user