Skip to content

Security and Privacy

Security Mechanisms

Authentication Systems

JWT (JSON Web Tokens)

Oktuple uses JWT for secure, stateless authentication across the system.

Token Structure:

typescript
interface JWTPayload {
  sub: string; // Subject (User ID)
  iss: string; // Issuer (Oktuple)
  aud: string; // Audience
  exp: number; // Expiration time
  iat: number; // Issued at
  scope: string[]; // Permissions
  org_id: string; // Organization ID
  jti: string; // JWT ID (unique identifier)
}

// Token lifecycle management
class TokenManager {
  async generateToken(user: User, permissions: Permission[]): Promise<string> {
    const payload: JWTPayload = {
      sub: user.id,
      iss: "oktuple",
      aud: "oktuple-users",
      exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour
      iat: Math.floor(Date.now() / 1000),
      scope: permissions.map((p) => p.name),
      org_id: user.organizationId,
      jti: generateUUID(),
    };

    return jwt.sign(payload, this.secretKey, { algorithm: "HS256" });
  }

  async validateToken(token: string): Promise<JWTPayload> {
    try {
      const decoded = jwt.verify(token, this.secretKey, {
        algorithms: ["HS256"],
      });
      return decoded as JWTPayload;
    } catch (error) {
      throw new Error("Invalid token");
    }
  }

  async refreshToken(refreshToken: string): Promise<string> {
    const storedToken = await this.getStoredRefreshToken(refreshToken);
    if (!storedToken || storedToken.expired) {
      throw new Error("Invalid refresh token");
    }

    const user = await this.getUserById(storedToken.userId);
    const permissions = await this.getUserPermissions(user.id);

    return this.generateToken(user, permissions);
  }
}

OAuth 2.0 Integration

Support for industry-standard OAuth 2.0 flows for third-party integrations.

Supported Flows:

typescript
enum OAuthFlow {
  AUTHORIZATION_CODE = "authorization_code",
  CLIENT_CREDENTIALS = "client_credentials",
  REFRESH_TOKEN = "refresh_token",
  IMPLICIT = "implicit",
}

interface OAuthClient {
  id: string;
  name: string;
  redirectUris: string[];
  clientSecret: string;
  allowedFlows: OAuthFlow[];
  scopes: string[];
  active: boolean;
}

class OAuthService {
  async authorizeClient(
    clientId: string,
    redirectUri: string,
    scope: string[],
    state: string
  ): Promise<string> {
    const client = await this.validateClient(clientId, redirectUri);

    const authCode = await this.generateAuthorizationCode({
      clientId: client.id,
      scope,
      state,
      expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes
    });

    return this.buildRedirectUrl(redirectUri, authCode, state);
  }

  async exchangeCodeForToken(
    code: string,
    clientId: string,
    clientSecret: string
  ): Promise<AccessToken> {
    const authCode = await this.validateAuthorizationCode(code, clientId);
    const client = await this.validateClientCredentials(clientId, clientSecret);

    const accessToken = await this.generateAccessToken({
      userId: authCode.userId,
      clientId: client.id,
      scope: authCode.scope,
    });

    await this.invalidateAuthorizationCode(code);

    return accessToken;
  }
}

Multi-Factor Authentication (MFA)

Enhanced security through multiple authentication factors.

MFA Implementation:

typescript
interface MFAConfig {
  enabled: boolean;
  methods: MFAMethod[];
  backupCodes: string[];
  lastUsed: Date;
}

enum MFAMethod {
  TOTP = "totp", // Time-based One-Time Password
  SMS = "sms", // SMS verification
  EMAIL = "email", // Email verification
  AUTHENTICATOR_APP = "authenticator_app", // Google Authenticator, etc.
}

class MFAService {
  async setupTOTP(userId: string): Promise<{ secret: string; qrCode: string }> {
    const secret = speakeasy.generateSecret({
      name: "Oktuple",
      issuer: "oktuple.com",
    });

    await this.storeMFASecret(userId, secret.base32);

    const qrCode = await this.generateQRCode(secret.otpauth_url);

    return {
      secret: secret.base32,
      qrCode,
    };
  }

  async verifyTOTP(userId: string, token: string): Promise<boolean> {
    const secret = await this.getMFASecret(userId);

    return speakeasy.totp.verify({
      secret,
      encoding: "base32",
      token,
      window: 2, // Allow 2 time steps for clock skew
    });
  }

  async generateBackupCodes(userId: string): Promise<string[]> {
    const codes = Array.from({ length: 10 }, () =>
      crypto.randomBytes(4).toString("hex").toUpperCase()
    );

    const hashedCodes = await Promise.all(
      codes.map((code) => bcrypt.hash(code, 10))
    );

    await this.storeBackupCodes(userId, hashedCodes);

    return codes;
  }
}

Authorization Models

Role-Based Access Control (RBAC)

Granular permission system based on user roles and organizational context.

RBAC Implementation:

typescript
interface Role {
  id: string;
  name: string;
  description: string;
  permissions: Permission[];
  organizationId: string;
  inheritedFrom?: string;
  active: boolean;
}

interface Permission {
  id: string;
  resource: string;
  action: string;
  conditions?: PermissionCondition[];
  scope: "global" | "organization" | "project" | "user";
}

interface PermissionCondition {
  field: string;
  operator: "eq" | "ne" | "in" | "not_in" | "gt" | "lt" | "gte" | "lte";
  value: any;
}

class RBACService {
  async checkPermission(
    userId: string,
    resource: string,
    action: string,
    context: any
  ): Promise<boolean> {
    const userRoles = await this.getUserRoles(userId);

    for (const role of userRoles) {
      const hasPermission = await this.roleHasPermission(
        role,
        resource,
        action,
        context
      );
      if (hasPermission) return true;
    }

    return false;
  }

  private async roleHasPermission(
    role: Role,
    resource: string,
    action: string,
    context: any
  ): Promise<boolean> {
    const permission = role.permissions.find(
      (p) => p.resource === resource && p.action === action
    );

    if (!permission) return false;

    // Check conditions if they exist
    if (permission.conditions && permission.conditions.length > 0) {
      return this.evaluateConditions(permission.conditions, context);
    }

    return true;
  }

  private evaluateConditions(
    conditions: PermissionCondition[],
    context: any
  ): boolean {
    return conditions.every((condition) => {
      const fieldValue = this.getNestedValue(context, condition.field);

      switch (condition.operator) {
        case "eq":
          return fieldValue === condition.value;
        case "ne":
          return fieldValue !== condition.value;
        case "in":
          return (
            Array.isArray(condition.value) &&
            condition.value.includes(fieldValue)
          );
        case "not_in":
          return (
            Array.isArray(condition.value) &&
            !condition.value.includes(fieldValue)
          );
        case "gt":
          return fieldValue > condition.value;
        case "lt":
          return fieldValue < condition.value;
        case "gte":
          return fieldValue >= condition.value;
        case "lte":
          return fieldValue <= condition.value;
        default:
          return false;
      }
    });
  }
}