mirror of
https://github.com/wassname/talk.git
synced 2026-07-18 12:40:13 +08:00
feat: added google support
This commit is contained in:
@@ -7,6 +7,7 @@ import passport, { Authenticator } from "passport";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import FacebookStrategy from "talk-server/app/middleware/passport/strategies/facebook";
|
||||
import GoogleStrategy from "talk-server/app/middleware/passport/strategies/google";
|
||||
import { JWTStrategy } from "talk-server/app/middleware/passport/strategies/jwt";
|
||||
import { createLocalStrategy } from "talk-server/app/middleware/passport/strategies/local";
|
||||
import OIDCStrategy from "talk-server/app/middleware/passport/strategies/oidc";
|
||||
@@ -54,6 +55,9 @@ export function createPassport(
|
||||
// Use the FacebookStrategy.
|
||||
auth.use(new FacebookStrategy(options));
|
||||
|
||||
// Use the GoogleStrategy.
|
||||
auth.use(new GoogleStrategy(options));
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ export default class FacebookStrategy extends OAuth2Strategy<
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe update user details?
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Db } from "mongodb";
|
||||
import { Profile, Strategy } from "passport-google-oauth2";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import OAuth2Strategy from "talk-server/app/middleware/passport/strategies/oauth2";
|
||||
import { reconstructTenantURL } from "talk-server/app/url";
|
||||
import {
|
||||
GQLAuthIntegrations,
|
||||
GQLGoogleAuthIntegration,
|
||||
GQLUSER_ROLE,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import {
|
||||
GoogleProfile,
|
||||
retrieveUserWithProfile,
|
||||
} from "talk-server/models/user";
|
||||
import TenantCache from "talk-server/services/tenant/cache";
|
||||
import { upsert } from "talk-server/services/users";
|
||||
|
||||
export interface GoogleStrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
}
|
||||
|
||||
export interface GoogleStrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
}
|
||||
|
||||
export default class GoogleStrategy extends OAuth2Strategy<
|
||||
GQLGoogleAuthIntegration,
|
||||
Strategy
|
||||
> {
|
||||
public name = "google";
|
||||
|
||||
constructor(options: GoogleStrategyOptions) {
|
||||
super({
|
||||
...options,
|
||||
scope: ["profile"],
|
||||
});
|
||||
}
|
||||
|
||||
protected getIntegration = (integrations: GQLAuthIntegrations) =>
|
||||
integrations.google;
|
||||
|
||||
protected async findOrCreateUser(
|
||||
tenant: Tenant,
|
||||
integration: Required<GQLGoogleAuthIntegration>,
|
||||
{ id, photos, emails, displayName }: Profile
|
||||
) {
|
||||
// Create the user profile that will be used to lookup the User.
|
||||
const profile: GoogleProfile = {
|
||||
type: "google",
|
||||
id,
|
||||
};
|
||||
|
||||
let user = await retrieveUserWithProfile(this.mongo, tenant.id, profile);
|
||||
if (!user) {
|
||||
if (!integration.allowRegistration) {
|
||||
// Registration is disabled, so we can't create the user user here.
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: implement rules.
|
||||
|
||||
// Try to get the avatar.
|
||||
let avatar: string | undefined;
|
||||
if (photos && photos.length > 0) {
|
||||
avatar = photos[0].value;
|
||||
}
|
||||
|
||||
// Try to get the email address.
|
||||
let email: string | undefined;
|
||||
let emailVerified: boolean | undefined;
|
||||
if (emails && emails.length > 0) {
|
||||
email = emails[0].value;
|
||||
emailVerified = false;
|
||||
}
|
||||
|
||||
user = await upsert(this.mongo, tenant, {
|
||||
username: null,
|
||||
displayName,
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
email,
|
||||
email_verified: emailVerified,
|
||||
avatar,
|
||||
profiles: [profile],
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe update user details?
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
protected createStrategy(
|
||||
tenant: Tenant,
|
||||
integration: Required<GQLGoogleAuthIntegration>
|
||||
) {
|
||||
return new Strategy(
|
||||
{
|
||||
clientID: integration.clientID,
|
||||
clientSecret: integration.clientSecret,
|
||||
callbackURL: reconstructTenantURL(
|
||||
this.config,
|
||||
tenant,
|
||||
"/api/tenant/auth/google/callback"
|
||||
),
|
||||
passReqToCallback: true,
|
||||
},
|
||||
this.verifyCallback
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export interface OAuth2StrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
scope?: string[];
|
||||
}
|
||||
|
||||
export default abstract class OAuth2Strategy<
|
||||
@@ -30,13 +31,15 @@ export default abstract class OAuth2Strategy<
|
||||
protected config: Config;
|
||||
protected mongo: Db;
|
||||
protected cache: TenantCacheAdapter<U>;
|
||||
private scope?: string[];
|
||||
|
||||
constructor({ config, mongo, tenantCache }: OAuth2StrategyOptions) {
|
||||
constructor({ config, mongo, tenantCache, scope }: OAuth2StrategyOptions) {
|
||||
super();
|
||||
|
||||
this.config = config;
|
||||
this.mongo = mongo;
|
||||
this.cache = new TenantCacheAdapter(tenantCache);
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected abstract getIntegration(integrations: GQLAuthIntegrations): T;
|
||||
@@ -114,7 +117,10 @@ export default abstract class OAuth2Strategy<
|
||||
strategy.redirect = this.redirect.bind(this);
|
||||
strategy.success = this.success.bind(this);
|
||||
|
||||
strategy.authenticate(req, { session: false });
|
||||
strategy.authenticate(req, {
|
||||
session: false,
|
||||
scope: this.scope,
|
||||
});
|
||||
} catch (err) {
|
||||
return this.error(err);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,30 @@ import {
|
||||
import { wrapAuthn } from "talk-server/app/middleware/passport";
|
||||
import { RouterOptions } from "talk-server/app/router/types";
|
||||
|
||||
function wrapPath(
|
||||
app: AppOptions,
|
||||
options: RouterOptions,
|
||||
router: express.Router,
|
||||
strategy: string,
|
||||
path: string = `/${strategy}`
|
||||
) {
|
||||
const handler = wrapAuthn(options.passport, app.signingConfig, strategy);
|
||||
|
||||
router.get(path, handler);
|
||||
router.get(path + "/callback", handler);
|
||||
}
|
||||
|
||||
export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
|
||||
const router = express.Router();
|
||||
|
||||
// Mount the passport routes.
|
||||
// Mount the logout handler.
|
||||
router.delete(
|
||||
"/",
|
||||
options.passport.authenticate("jwt", { session: false }),
|
||||
logoutHandler({ redis: app.redis })
|
||||
);
|
||||
|
||||
// Mount the Local Authentication handlers.
|
||||
router.post(
|
||||
"/local",
|
||||
express.json(),
|
||||
@@ -29,24 +43,10 @@ export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
|
||||
signupHandler({ db: app.mongo, signingConfig: app.signingConfig })
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/facebook",
|
||||
wrapAuthn(options.passport, app.signingConfig, "facebook")
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/facebook/callback",
|
||||
wrapAuthn(options.passport, app.signingConfig, "facebook")
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/oidc/:oidcID",
|
||||
wrapAuthn(options.passport, app.signingConfig, "oidc")
|
||||
);
|
||||
router.get(
|
||||
"/oidc/:oidcID/callback",
|
||||
wrapAuthn(options.passport, app.signingConfig, "oidc")
|
||||
);
|
||||
// Mount the external auth integrations with middleware/handle wrappers.
|
||||
wrapPath(app, options, router, "facebook");
|
||||
wrapPath(app, options, router, "google");
|
||||
wrapPath(app, options, router, "oidc", "/oidc/:oidc");
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,17 @@ export interface FacebookProfile {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type Profile = LocalProfile | OIDCProfile | SSOProfile | FacebookProfile;
|
||||
export interface GoogleProfile {
|
||||
type: "google";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type Profile =
|
||||
| LocalProfile
|
||||
| OIDCProfile
|
||||
| SSOProfile
|
||||
| FacebookProfile
|
||||
| GoogleProfile;
|
||||
|
||||
export interface Token {
|
||||
readonly id: string;
|
||||
|
||||
Reference in New Issue
Block a user