Heim  >  Artikel  >  Web-Frontend  >  Verlagerung der Benutzerverwaltung von unternehmensintern auf ein Produkt: Warum wir es getan haben und was wir daraus gelernt haben

Verlagerung der Benutzerverwaltung von unternehmensintern auf ein Produkt: Warum wir es getan haben und was wir daraus gelernt haben

PHPz
PHPzOriginal
2024-08-13 06:40:09375Durchsuche

Inhaltsverzeichnis

  • TL;DR
  • Unser Hauptziel
  • Forschung und Bewertung
  • Implementierung
  • JWT-Strategie vs. Clerk-Strategie
  • Synchronisierung zwischen Clerk und Novu
    • Benutzer und Organisationen synchronisieren
    • Was wird in Clerk vs Novu gespeichert?
  • Einbindung von Enterprise-Edition-Anbietern
    • AuthService & AuthModule – Dynamische Injektion
    • Repositorys – Benutzer, Organisationen, Mitglieder
    • Controller
    • Probleme mit diesem Ansatz
  • Endpunktänderung
  • Wichtige Punkte, die Sie berücksichtigen und vermeiden sollten
  • Team-Spotlight
  • Zusammenfassung
  • Rückblick-Bonuspunkte

Dieser Beitrag wurde Ihnen von Novu präsentiert

Moving User Management from In-House to a Product: Why We Did It and What We Learned novuhq / Novu

Open-Source-Benachrichtigungsplattform. Einbettbares Benachrichtigungscenter, E-Mail-, Push- und Slack-Integrationen.

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Moving User Management from In-House to a Product: Why We Did It and What We Learned Moving User Management from In-House to a Product: Why We Did It and What We Learned Moving User Management from In-House to a Product: Why We Did It and What We Learned

Die Open-Source-Benachrichtigungsinfrastruktur für Entwickler

Der ultimative Service zur Verwaltung von Mehrkanal-Benachrichtigungen mit einer einzigen API


Entdecken Sie die Dokumente »

Fehler melden · Anforderungsfunktion · Treten Sie unserem Discord bei · Roadmap · X · Benachrichtigungsverzeichnis

⭐️ Warum Novu?

Novu bietet eine einheitliche API, die das Senden von Benachrichtigungen über mehrere Kanäle, einschließlich In-App, Push, E-Mail, SMS und Chat, vereinfacht. Mit Novu können Sie benutzerdefinierte Workflows erstellen und Bedingungen für jeden Kanal definieren, um sicherzustellen, dass Ihre Benachrichtigungen so effektiv wie möglich zugestellt werden.

✨ Funktionen

  • ? Eine einzige API für alle Messaging-Anbieter (In-App, E-Mail, SMS, Push, Chat)
  • ? Vollständig verwalteter GitOps Flow, bereitgestellt von Ihrem CI
  • ? Definieren Sie Workflow- und Schrittvalidierungen mit Zod oder JSON Schema
  • ? Reagieren Sie auf E-Mail-/Maizzle-/MJML-Integrationen
  • ? Ausgestattet mit einem CMS für erweiterte Layouts und Designverwaltung
  • ? Debuggen und analysieren Sie Mehrkanalnachrichten in einem einzigen Dashboard
  • ? Einbettbares Benachrichtigungscenter…
Auf GitHub ansehen

TL;DR

Novu implementierte Clerk als Benutzerverwaltungslösung (Authentifizierungsinfrastruktur), die den Grundstein für die Bereitstellung von SAML Single Sign-On (SSO)-Funktionalität, Google und GitHub als OAuth-Anbieter, Multi-Faktor-Authentifizierung und rollenbasierter Kontosteuerung (RBAC) legte ) und mehr.

Ein Entwickler namens Adam hat es mit tatkräftiger Unterstützung des Plattformingenieurs Denis implementiert.


Moving User Management from In-House to a Product: Why We Did It and What We Learned

Wie die meisten Projekte begann auch dieses mit dem Rückstand. Nicht bevor Dutzende von Kunden danach gefragt und die Anfrage in unserer Roadmap stark positiv bewertet haben.

Als Benachrichtigungsinfrastrukturlösung umfasst ein Teil unserer App und Architektur die Verwaltung von Benutzern anhand der Registrierungen, Anmeldungen und Sitzungsprotokolle, damit Benutzer Teammitglieder in die Organisation einladen und den Zugriff jeder Rolle verwalten können.

Es geht um Prioritäten. Das Hauptaugenmerk von Novu liegt darauf, alle Probleme im Zusammenhang mit Benachrichtigungen und Nachrichtenverwaltung zu lösen, damit unsere Benutzer dies nicht tun müssen. Deshalb verbringen wir unsere Zeit damit, die bestmögliche Erfahrung beim Erstellen und Verwalten von Benachrichtigungsworkflows zu bieten, Entwicklern die Möglichkeit zu geben, ihre Zeit zu sparen, und die Zusammenarbeit zwischen Produkt- und Marketingteams zu optimieren.
Eine effektive Benutzerverwaltung fällt nicht unter den „Kernwert“, dem wir uns verschrieben haben.

So wie wir von Ihnen erwarten, dass Sie die Last der technischen Benachrichtigungen auf unser Fachwissen übertragen, haben wir die Last der technischen effektiven Benutzerverwaltung auf die Fachkompetenz von Clerk verlagert.

Unnötig zu erwähnen, dass unser Team vom ersten Tag an intern eine großartige Authentifizierungs- und Autorisierungsinfrastruktur aufgebaut hat, die auf einer maßgeschneiderten und gut gestalteten Architektur basiert.

Mit zunehmendem Level konzentrieren wir uns noch mehr auf die Perfektionierung des Benachrichtigungsentwicklungserlebnisses.

Wir erwarten von Entwicklern und Ingenieuren, dass sie das Rad nicht neu erfinden und Novu die Verwaltung von Benachrichtigungen überlassen, genauso wie wir uns dafür entscheiden, bewährte, getestete und führende Lösungen für andere Aspekte unseres Produkts zu nutzen: MongoDB für die Datenbank, Stripe für Zahlungen und jetzt Sachbearbeiter für Benutzerverwaltung. Wir lassen den Worten Taten folgen.


Unser oberstes Ziel

Schaffen Sie ein sicheres und benutzerfreundliches Erlebnis für unsere Benutzer.

Wenn man den ersten Entwurf für dieses Projekt skizziert, wirkt er möglicherweise kurz und unkompliziert und erweckt möglicherweise sogar den Eindruck, dass er an einem Wochenende fertiggestellt werden könnte.

Die Checkliste für den ersten Entwurf:

  • OAuth-Anbieter (GitHub, Google)
  • SAML SSO
  • Sichere Sitzungsverwaltung
  • RBAC
  • Magic-Link-Authentifizierung

Beachten Sie, dass das Projekt nicht genügend Feedback und Input erhalten hat, wenn sich der ursprüngliche Entwurf nicht geändert hat. Natürlich ist die Liste länger geworden.

Die aktuelle Checkliste:

  • Melden Sie sich mit Benutzeranmeldeinformationen an
  • Melden Sie sich bei OAuth-Anbietern (Github, Google) an
  • Melden Sie sich mit Benutzeranmeldeinformationen an
  • Melden Sie sich bei OAuth-Anbietern (Github, Google) an
  • Mit SSO (SAML) anmelden
  • Melden Sie sich über Novu CLI an
  • Anmelden/Registrieren beim Vercel Marketplace
  • Organisation erstellen
  • Organisationsmanagement
  • Benutzerverwaltung (Benutzerinformationen, Anmeldeinformationen usw. aktualisieren)
  • MFA/2FA (OTP per SMS/E-Mail, TOTP, Passkey, biometrisch usw.)
  • Einladungen
  • RBAC: Zwei Rollen Administrator und Redakteur
    • admin = Der Administrator kann auf jede Seite der Webplattform zugreifen und mit ihr interagieren (also auch Teammitglieder und Einstellungen)
    • Redakteur = Die Rolle des Redakteurs bleibt der „Hauptinhaltsmanager“ (auch bekannt als Produktmanager oder Marketingmanager)

Forschung und Bewertung

Nachdem der Umfang des Projekts ermittelt wurde, besteht der nächste Schritt darin, Recherchen durchzuführen und die Ressourcen zu bewerten, die zum Erreichen des gewünschten Ergebnisses erforderlich sind.

Dieser Prozess umfasst:

  • Sie müssen den aktuellen Zustand und jede Schicht des Produkts genau kennen:

    • Abhängigkeiten
    • Endpunkte
    • Architektur
    • Komponenten und Darstellung der Client-Ebene (Frontend)
    • Tests

    Und mehr.

  • Beschreiben Sie die Migrationsspezifikationen (was intern bleibt und verdrängt werden sollte)

  • Abwärtskompatibilität

  • Versuchen Sie, Hinweise auf ähnliche Projekte zu finden, vielleicht von ehemaligen Kollegen, und lernen Sie aus deren Prozess und Empfehlungen

  • Versuchen Sie, Open-Source-Lösungen zu finden

  • Finden Sie heraus, ob es Anbieter (Lösungen von Drittanbietern) gibt, und vergleichen Sie diese.

Und mehr.

In einem weiteren Blogbeitrag werden wir untersuchen, wie wir Lösungen (oder Produkte) von Drittanbietern als Service/Infrastruktur als Dienstleistungsunternehmen bewerten und vergleichen.

Unzureichende Recherche oder ungenaue Bewertung führen in der Regel zu technischen Schulden und zukünftigen Ressourcenverlusten, wie z. B. Engineering-Zeit beim Hinzufügen zusätzlicher Funktionen und Wartung, die eine Umgestaltung der gesamten Sache erfordern. Suchen Sie also nach den versteckten Kosten jeder Option.

Erfahrene Teamleiter wissen, wie sie den Return on Investment (ROI) jeder Option bewerten, was ihnen hilft, die beste Entscheidung für das Unternehmen zu treffen.

Genau so sind wir bei Clerk gelandet. Ihre Lösung deckt die meisten unserer Anwendungsfälle ab und aus geschäftlicher Sicht ist der ROI bei der Implementierung zur Verwaltung der Benutzer- und Organisationsebene sinnvoll.


Durchführung

Der Novu-Dienst enthält viele Mikrodienste und Aspekte wie:

  • Benachrichtigungskanäle (SMS, E-Mail, In-App, Push, Chat usw.),
  • Benachrichtigungs-Orchestrierung (geräteübergreifende Synchronisierung, Digest Engine, Verzögerung, Zeitzonenerkennung usw.)
  • Beobachtbarkeit von Benachrichtigungen (Debugging, Einblicke usw.)
  • Benachrichtigungsinhaltsverwaltung (Editor, Branding, Layouts, Übersetzungen, Variablenverwaltung usw.)
  • Endbenutzerverwaltung (Benutzereinstellungen, Abonnenten, Themen, Segmente, Abonnementverwaltung usw.)
  • Kontoverwaltung (SSO, rollenbasierte Zugriffskontrolle, Mandantenfähigkeit, Abrechnung usw.)

Das Diagramm unten zeigt eine vereinfachte Version der API-Struktur von Novu, die sich nur auf die Authentifizierung und Autorisierung von Novu-Benutzern und -Organisationen vor der Implementierung von Clerk konzentriert.

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Wir verwenden MongoDB, um alle Daten zu speichern, die Novu benötigt, jeden Benutzer, jede Organisation, jeden Mieter, jeden Abonnenten, jedes Thema … kurz gesagt, alles.

Da Clerk über eine eigene Datenbank zur Benutzerverwaltung verfügt, mussten wir die Migration und Synchronisierung zwischen den Datenbanken sehr sorgfältig und präzise durchführen.


JWT-Strategie vs. Clerk-Strategie

Eines der wichtigsten Dinge, die wir sicherstellen mussten, ist, dass sich das UserSessionData-Objekt nicht ändert, um die Sitzung des Benutzers bei der Verwendung von Novu nicht zu unterbrechen. Es sollte kompatibel bleiben.

Hier können Sie das Beispiel der jwt.stratgy.ts-Datei sehen:

//jwt.stratgy.ts
import type http from 'http';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ApiAuthSchemeEnum, HttpRequestHeaderKeysEnum, UserSessionData } from '@novu/shared';
import { AuthService, Instrument } from '@novu/application-generic';
import { EnvironmentRepository } from '@novu/dal';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService, private environmentRepository: EnvironmentRepository) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
      passReqToCallback: true,
    });
  }
  @Instrument()
  async validate(req: http.IncomingMessage, session: UserSessionData) {
    // Set the scheme to Bearer, meaning the user is authenticated via a JWT coming from Dashboard
    session.scheme = ApiAuthSchemeEnum.BEARER;

    const user = await this.authService.validateUser(session);
    if (!user) {
      throw new UnauthorizedException();
    }

    await this.resolveEnvironmentId(req, session);

    return session;
  }

  @Instrument()
  async resolveEnvironmentId(req: http.IncomingMessage, session: UserSessionData) {
    // Fetch the environmentId from the request header
    const environmentIdFromHeader =
      (req.headers[HttpRequestHeaderKeysEnum.NOVU_ENVIRONMENT_ID.toLowerCase()] as string) || '';

    /*
     * Ensure backwards compatibility with existing JWTs that contain environmentId
     * or cached SPA versions of Dashboard as there is no guarantee all current users
     * will have environmentId in localStorage instantly after the deployment.
     */
    const environmentIdFromLegacyAuthToken = session.environmentId;

    let currentEnvironmentId = '';

    if (environmentIdFromLegacyAuthToken) {
      currentEnvironmentId = environmentIdFromLegacyAuthToken;
    } else {
      const environments = await this.environmentRepository.findOrganizationEnvironments(session.organizationId);
      const environmentIds = environments.map((env) => env._id);
      const developmentEnvironmentId = environments.find((env) => env.name === 'Development')?._id || '';

      currentEnvironmentId = developmentEnvironmentId;

      if (environmentIds.includes(environmentIdFromHeader)) {
        currentEnvironmentId = environmentIdFromHeader;
      }
    }

    session.environmentId = currentEnvironmentId;
  }
}

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Um die Kompatibilität mit dem Rest der App aufrechtzuerhalten, mussten wir die JWT-Nutzlast von Clerk in das zuvor vorhandene JWT-Format umwandeln.

So haben wir es gemacht:

async validate(payload: ClerkJwtPayload): Promise<IJwtClaims> {
  const jwtClaims: IJwtClaims = {
    // first time its clerk_id, after sync its novu internal id
    _id: payload.externalId || payload._id,
    firstName: payload.firstName,
    lastName: payload.lastName,
    email: payload.email,
    profilePicture: payload.profilePicture,
    // first time its clerk id, after sync its novu internal id
    organizationId: payload.externalOrgId || payload.org_id,
    environmentId: payload.environmentId,
    roles: payload.org_role ? [payload.org_role.replace('org:', '')] : [],
    exp: payload.exp,
  };

  return jwtClaims;
}

Hier können Sie das Dateibeispiel clerk.strategy.ts sehen:

import type http from 'http';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import {
  ApiAuthSchemeEnum,
  ClerkJwtPayload,
  HttpRequestHeaderKeysEnum,
  PassportStrategyEnum,
  UserSessionData,
} from '@novu/shared';
import { EnvironmentRepository, EnvironmentEntity } from '@novu/dal';
import { LinkEntitiesService } from '../services/link-entities.service';

@Injectable()
export class ClerkStrategy extends PassportStrategy(Strategy, PassportStrategyEnum.JWT_CLERK) {
  constructor(private environmentRepository: EnvironmentRepository, private linkEntitiesService: LinkEntitiesService) {
    super({
      // ...configuration details
    });
  }

  async validate(req: http.IncomingMessage, payload: ClerkJwtPayload) {
    const { internalUserId, internalOrgId } = await this.linkEntitiesService.linkInternalExternalEntities(req, payload);

    const session: UserSessionData = {
      _id: internalUserId,
      firstName: payload.firstName,
      lastName: payload.lastName,
      email: payload.email,
      profilePicture: payload.profilePicture,
      organizationId: internalOrgId,
      roles: payload.org_role ? [payload.org_role.replace('org:', '')] : [],
      exp: payload.exp,
      iss: payload.iss,
      scheme: ApiAuthSchemeEnum.BEARER,
      environmentId: undefined,
    };

    await this.resolveEnvironmentId(req, session);

    return session;
  }

  // Other functions...
}

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Synchronisierung zwischen Clerk und Novu

Während das Ziel darin besteht, idealerweise nur Clerk zum Erstellen und Abrufen von Benutzern, Organisationen usw. zu verwenden, ist dies leider nicht vollständig möglich, da einige Metadaten über Benutzer und Organisationen auf performante Weise gespeichert und abgefragt werden müssen.

Hier ist ein Beispiel für eine Methode im Organisations-Repository von Novu:

  async findPartnerConfigurationDetails(organizationId: string, userId: string, configurationId: string) {
    const organizationIds = await this.getUsersMembersOrganizationIds(userId);

    return await this.find(
      {
        _id: { $in: organizationIds },
        'partnerConfigurations.configurationId': configurationId,
      },
      { 'partnerConfigurations.$': 1 }
    );
  }

Diese Methode verwendet verschiedene MongoDB-spezifische Konstrukte, um ein Dokument zu filtern. Dies kann mit Clerk nicht auf performante Weise reproduziert werden, da es sich nicht um eine Datenbank handelt, die für solche Abfragen gedacht ist.

Was wir tun können, ist, diese Metadaten über die Organisation in unserer MongoDB-Organisationssammlung zu speichern und die Sammlung mithilfe von externalId mit der Clerk-Datenbank zu verknüpfen/synchronisieren.

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Jetzt können wir Clerk und MongoDB kombinieren, um bei Bedarf die Metadaten abzufragen.

async findPartnerConfigurationDetails(
  organizationId: string,
  userId: string,
  configurationId: string
): Promise<OrganizationEntity[]> {
  const clerkOrganizations = await this.getUsersMembersOrganizations(userId);

  return await this.communityOrganizationRepository.find(
    {
      _id: { $in: clerkOrganizations.map((org) => org.id) },
      'partnerConfigurations.configurationId': configurationId,
    },
    { 'partnerConfigurations.$': 1 }
  );
}

private async getUsersMembersOrganizations(userId: string): Promise<Organization[]> {
  const userOrgMemberships = await this.clerkClient.users.getOrganizationMembershipList({
    userId,
  });

  return userOrgMemberships.data.map((membership) => membership.organization);
}

Durch den Aufruf von getUsersMembersOrganizations ruft findPartnerConfigurationDetails die erforderlichen Organisationsdaten ab, um eine gefilterte Suche im communityOrganizationRepository durchzuführen und sicherzustellen, dass nur relevante Konfigurationen zurückgegeben werden.

Wir müssen nur Benutzer und Organisationen zwischen Clerk und Novu synchronisieren, die Organisationsmitglieder müssen nicht synchronisiert werden.


Benutzer und Organisationen synchronisieren

Es gibt zwei Möglichkeiten, wie die Datenbank-IDs synchronisiert werden:

  • middleware - any endpoint in API will sync the IDs if it detects that JWT doesn’t yet contain an internal ID.
  • webhook - as soon as the user/org is registered in Clerk, Clerk calls Novu’s API webhook, and we sync it.

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Here is the flow we had in mind:

  1. A user creates a new account via frontend using the Clerk component
  2. Gets a new JWT containing Clerk user-id
  3. Any request that hits the API triggers the syncing process (given it hasn’t yet happened)
  4. A new user is created in Novu’s MongoDB containing the Clerk’s externalId
  5. Clerk user object gets updated with Novu internal object id (saved as externalId in Clerk)
  6. The new token returned from Clerk now contains an externalId that is equal to Novu's internal user ID.
  7. In the Clerk strategy in validate() function on API - we set _id to equal to externalId so it is compatible with the rest of the app.

Note
In the application, we always expect Novu’s internal id on input and we always return internal id on output - its important for the application to work as is without major changes to the rest of the code.
API expects internal _id everywhere and it needs to be MongoDB ObjectID type, because it parses this user id back to ObjectID e.g. when creating new environment or any other entity which needs reference to user.

The same logic applies to organizations; just the endpoint is different.


What is stored in Clerk vs Novu

Users

For the users, we store everything in Clerk. All the properties are mostly just simple key/value pairs and we don’t need any advanced filtering on them, therefore they can be retrieved and updated directly in Clerk.

In internal MongoDB, we store just the user internal and external ids.

The original Novu user properties are stored in Clerk’s publicMetadata :

export type UserPublicMetadata = {
  profilePicture?: string | null;
  showOnBoardingTour?: number;
};

There are also many other attributes coming from Clerk which can be set on the user.

Organizations

For the organizations, we store everything in Clerk except for apiServiceLevel, partnerConfigurations, and branding since they are “native” to Clerk and we update those attributes directly there via frontend components and so we don’t need to sync with our internal DB after we change organization name or logo via Clerk component.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Injection of Enterprise Edition providers

The goal here was to replace the community (open source) implementation with Clerk while being minimally invasive to the application and to keep the Clerk implementation in a separate package.

This means we need to keep the changed providers (OrganizationRepository, AuthService…) on the same place with the same name so we don’t break the imports all over the place, but we need to change their body to be different based on feature flag.

The other option would be to change all of these providers in the 100+ of files and then import the EE(enterprise edition) package everywhere, which is probably not a good idea.

This turned out to be quite challenging due to the fact that users, organization and members are relatively deeply integrated to the application itself, referenced in a lot of places and they’re also tied to MongoDB specifics such as ObjectID or queries (create, update, findOne …).

The idea is to provide different implementation using NestJS dynamic custom providers where we are able to inject different class/service on compile time based on the enterprise feature flag.

This is the most promising solution we found while keeping the rest of the app mostly untouched, there are some drawbacks explained later.


AuthService & AuthModule - dynamic injection

Moving User Management from In-House to a Product: Why We Did It and What We Learned

We have two implementations of AuthService - community and enterprise one (in private package), we inject one of those as AUTH_SERVICE provider.

We need to however have a common interface for both IAuthService

Since we also need to change the AuthModule, we initialize two different modules based on the feature flag like this:

function getModuleConfig(): ModuleMetadata {
  if (process.env.NOVU_ENTERPRISE === 'true') {
    return getEEModuleConfig();
  } else {
    return getCommunityAuthModuleConfig();
  }
}

@Global()
@Module(getModuleConfig())
export class AuthModule {
  public configure(consumer: MiddlewareConsumer) {
    if (process.env.NOVU_ENTERPRISE !== 'true') {
      configure(consumer);
    }
  }
}

The reason why the EEModule can be a standalone module in the @novu/ee-auth package which we would just import instead of the original AuthModule and instead we are initializing one module conditionally inside API, is that we are reusing some original providers in the EE one - e.g. ApiKeyStrategy , RolesGuard, EnvironmentGuard etc which resides directly in API.

We would need to import them in the @novu/ee-auth package which would require to export these things somewhere (probably in some shared package) and it introduces other issues like circular deps etc - it can be however refactored later.

Repositories - users, organizations, members

Same logic applies for the repositories. No module is being initialized here, they’re just directly injected to the original repository classes.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Controllers

The controllers are being conditionally imported from inside @novu/api . The reason for that is the same as in the auth module, there are too many imports that the controllers uses, that we would either need to move to @novu/ee-auth or move them to a separate shared package - which would then trigger a much bigger change to the other unrelated parts of the app, which would increase the scope of this change.

function getControllers() {
  if (process.env.NOVU_ENTERPRISE === 'true') {
    return [EEOrganizationController];
  }

  return [OrganizationController];
}

@Module({
  controllers: [...getControllers()],
})
export class OrganizationModule implements NestModule {
    ...
}


Issues with this approach

The main issue here is the need for common interface for both of the classes - community and enterprise. You want to remain compatible in both community and enterprise versions, so when there is a this.organizationService.getOrganizations() method being called in 50 places in the app - you need an enterprise equivalent with the same name otherwise you need to change 50 places to call something else.

This results in not-so-strict typing and methods without implementation

Moving User Management from In-House to a Product: Why We Did It and What We Learned

We need to have a common interface for both, however the community one relies on MongoDB methods and needs different method arguments as the enterprise one which causes a use of any to forcefully fit both classes etc.
In some cases we don’t need the method at all, so we need to throw Not Implemented .


Endpoints modification

We modified the endpoints as follows:

  • AuthController: Removed and replaced by frontend calls to Clerk.
  • UserController: Removed, added a sync endpoint for Clerk users with MongoDB.
  • OrganizationController: Removed several endpoints, which can be migrated later.
  • InvitesController: Completely removed.
  • StorageModule: Completely removed.

Key points to consider and avoid

  1. Avoid Storing Frequently Changing Properties in JWT
    • Example: environmentID
    • It can be cumbersome to update these properties.
  2. Simplify Stored Data Structures
    • Avoid storing complex structures in user, organization, or member records.
    • Clerk performs optimally with simple key:value pairs, not arrays of objects.
  3. Implement a User/Organization Replication Mechanism
    • This helps bridge the gap during the migration period before Clerk is fully enabled.
    • Use MongoDB triggers to replicate newly created users and organizations to both Clerk and your internal database.
  4. Store Original Emails
    • Do not sanitize emails as Clerk uses the original email as a unique identifier.

Team Spotlight

Lead Engineer: Adam Chmara

Platform Team Lead: Denis Kralj


Summary

Our implementation approach comes to the fact that we offloaded the Users, Organizations and Members management to Clerk.

The data property injection to Novu’s Controllers (endpoints) layer, Business layer and data layer happens based on “Enterprise” feature flag validation.

We are leveraging pre-built Clerk components on the frontend and reducing the need to build and maintain our own custom implementation on the backend.

You can also observe below the diagram of the current state after implementing Clerk.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Rückblick-Bonuspunkte

Als wir uns entschieden haben, Clerk für die Benutzerverwaltung zu implementieren, haben wir uns auch für die langfristigen Vorteile der Erweiterung der Fähigkeiten und Funktionen entschieden, die Clerk in Zukunft unterstützen und anbieten wird.

Hier sind einige Beispiele dafür, was wir in naher Zukunft unterstützen könnten:

  • Feingranulare Zugriffskontrolle (FGAC)
    • Attributbasiert: FGAC wird häufig mithilfe der attributbasierten Zugriffskontrolle (ABAC) implementiert, bei der Zugriffsentscheidungen auf verschiedenen Attributen von Benutzern, Ressourcen und der Umgebung basieren. Zu den Attributen können Benutzerrolle, Abteilung, Ressourcentyp, Tageszeit und mehr gehören.
    • Flexibilität: FGAC bietet mehr Flexibilität und Granularität, indem es detaillierte, bedingungsbasierte Zugriffskontrollen ermöglicht. Dies bedeutet, dass Berechtigungen auf ganz bestimmte Szenarien abgestimmt werden können.
    • Dynamisch: FGAC kann sich dynamisch an Veränderungen in der Umgebung anpassen, wie z. B. zeitkritischer Zugriff oder standortbezogene Einschränkungen.
    • Detaillierte Berechtigungen: Berechtigungen in FGAC sind spezifischer und können auf einzelne Aktionen, Benutzer oder Situationen zugeschnitten werden.

Novu dieses Maß an detaillierter Flexibilität zu bieten, wäre aufgrund der potenziellen Komplexität der Implementierung möglicherweise außerhalb des Rahmens oder gar nicht möglich gewesen.

  • Benutzeridentitätswechsel

    Unsere Kundenerfolgs- oder Supportteams könnten dies nutzen, um Probleme zu beheben, Support bereitzustellen oder Funktionen aus der Perspektive des imitierten Benutzers zu testen, ohne dessen Passwort oder andere Authentifizierungsdetails kennen zu müssen.

    • Reduziert den Zeitaufwand und die Komplexität, die mit der Diagnose und Lösung von Benutzerproblemen verbunden sind.
    • Stellt sicher, dass die durchgeführten Support- oder Verwaltungsmaßnahmen korrekt sind, da das Support-Personal das System genau so sehen und mit ihm interagieren kann, wie es der Benutzer tun würde.
    • Steigert die Benutzerzufriedenheit durch schnelleren und effektiveren Support.

Einfach ausgedrückt können wir das Erlebnis für Novu-Benutzer problemlos verbessern, da die Authentifizierungsinfrastruktur jetzt dafür ausgelegt ist.


Wenn Sie zusätzliche Funktionen im Zusammenhang mit AuthN (oder anderen) vorschlagen möchten, besuchen Sie unsere Roadmap, um Anfragen zu überprüfen und positiv zu bewerten, oder reichen Sie Ihre Idee ein.


Gefällt Ihnen, was Sie gelesen haben? Klicken Sie auf „Folgen“, um weitere Updates zu erhalten, und hinterlassen Sie unten einen Kommentar. Ich würde ❤️ dein ?

hören
Moving User Management from In-House to a Product: Why We Did It and What We Learned

Emil Pearce

Ich schreibe Code und Wörter in Cafés.

Das obige ist der detaillierte Inhalt vonVerlagerung der Benutzerverwaltung von unternehmensintern auf ein Produkt: Warum wir es getan haben und was wir daraus gelernt haben. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn