tachybase_todo/packages/plugins/@nocobase/plugin-saml/src/server/saml-auth.ts
chenos b359f9eac6
feat: supports subdirectory deployment (#3731)
* feat: supports subdirectory deployment

* feat: auto publicPath

* fix: buildIndexHtml

* fix: format

* fix: regexp

* fix: test error

* fix: nocobase.conf

* fix: path

* fix: nocobase.conf

* fix: bugs

* fix: resourcer prefix

* fix: cas
2024-03-16 20:01:34 +08:00

95 lines
2.7 KiB
TypeScript

import { AuthConfig, BaseAuth } from '@nocobase/auth';
import { AuthModel } from '@nocobase/plugin-auth';
import { SAML, SamlConfig } from '@node-saml/node-saml';
interface SAMLOptions {
ssoUrl?: string;
certificate?: string;
idpIssuer?: string;
http?: boolean;
}
export class SAMLAuth extends BaseAuth {
constructor(config: AuthConfig) {
const { ctx } = config;
super({
...config,
userCollection: ctx.db.getCollection('users'),
});
}
getOptions(): SamlConfig {
const ctx = this.ctx;
const { ssoUrl, certificate, idpIssuer, http }: SAMLOptions = this.options?.saml || {};
const name = this.authenticator.get('name');
const protocol = http ? 'http' : 'https';
return {
callbackUrl: `${protocol}://${ctx.host}${process.env.API_BASE_PATH}saml:redirect?authenticator=${name}&__appName=${ctx.app.name}`,
entryPoint: ssoUrl,
issuer: name,
cert: certificate,
idpIssuer,
wantAssertionsSigned: false,
};
}
async validate() {
const ctx = this.ctx;
const {
params: { values: samlResponse },
} = ctx.action;
const saml = new SAML(this.getOptions());
const { profile } = await saml.validatePostResponseAsync(samlResponse);
const { nameID, nickname, firstName, lastName, phone } = profile;
let { email, username } = profile;
const isEmail = nameID.match(/^.+@.+\..+$/);
if (!email && isEmail) {
email = nameID;
}
if (!username && !isEmail) {
username = nameID;
}
const authenticator = this.authenticator as AuthModel;
let user = await authenticator.findUser(nameID);
if (user) {
return user;
}
// Bind existed user
const { userBindField = 'email' } = this.options?.saml || {};
if (userBindField === 'email' && email) {
user = await this.userRepository.findOne({
filter: { email },
});
} else if (userBindField === 'username' && username) {
user = await this.userRepository.findOne({
filter: { username },
});
}
if (user) {
await this.authenticator.addUser(user.id, {
through: {
uuid: nameID,
},
});
return user;
}
// Create new user
const { autoSignup } = this.options?.public || {};
if (!autoSignup) {
throw new Error('User not found');
}
if (username && !this.validateUsername(username as string)) {
throw new Error('Username must be 2-16 characters in length (excluding @.<>"\'/)');
}
const fullName = firstName && lastName && `${firstName} ${lastName}`;
return await authenticator.newUser(nameID, {
username: username ?? null,
nickname: nickname || fullName || username || nameID,
email: email ?? null,
phone: phone ?? null,
});
}
}