ircu2/tools/iauthd-ts/src/auth/config.ts

186 lines
4.3 KiB
TypeScript

/**
* Authentication configuration parser
* Parses #IAUTH AUTH directives
*/
import type {
AuthProviderConfig,
FileAuthConfig,
LDAPAuthConfig,
KeycloakAuthConfig,
} from './types.js';
/**
* Parse an AUTH configuration line
* Format: provider=<type> key=value key=value ...
*
* Examples:
* provider=file path=/path/to/users
* provider=ldap uri=ldap://server:389 mode=direct userdn=uid=%s,ou=users,dc=example,dc=com
* provider=ldap uri=ldaps://server:636 mode=search basedn=ou=users,dc=example,dc=com ...
*/
export function parseAuthConfig(args: string): AuthProviderConfig | null {
const params = parseKeyValuePairs(args);
const provider = params.get('provider');
if (!provider) {
return null;
}
switch (provider) {
case 'file':
return parseFileAuthConfig(params);
case 'ldap':
return parseLDAPAuthConfig(params);
case 'keycloak':
return parseKeycloakAuthConfig(params);
default:
console.error(`Unknown auth provider: ${provider}`);
return null;
}
}
/**
* Parse key=value pairs from a string
* Handles quoted values for values containing spaces
*/
function parseKeyValuePairs(args: string): Map<string, string> {
const result = new Map<string, string>();
// Match key=value or key="value with spaces" or key='value with spaces'
const regex = /(\w+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g;
let match;
while ((match = regex.exec(args)) !== null) {
const key = match[1];
// Use quoted value if present, otherwise unquoted
const value = match[2] ?? match[3] ?? match[4];
result.set(key, value);
}
return result;
}
/**
* Parse file auth provider config
*/
function parseFileAuthConfig(params: Map<string, string>): FileAuthConfig | null {
const path = params.get('path');
if (!path) {
console.error('File auth provider requires path');
return null;
}
const priority = params.has('priority')
? parseInt(params.get('priority')!, 10)
: undefined;
return {
provider: 'file',
path,
priority,
};
}
/**
* Parse LDAP auth provider config
*/
function parseLDAPAuthConfig(params: Map<string, string>): LDAPAuthConfig | null {
const uri = params.get('uri');
if (!uri) {
console.error('LDAP auth provider requires uri');
return null;
}
const mode = params.get('mode') as 'direct' | 'search' | undefined;
if (!mode || (mode !== 'direct' && mode !== 'search')) {
console.error('LDAP auth provider requires mode (direct or search)');
return null;
}
const priority = params.has('priority')
? parseInt(params.get('priority')!, 10)
: undefined;
const timeout = params.has('timeout')
? parseInt(params.get('timeout')!, 10)
: undefined;
const config: LDAPAuthConfig = {
provider: 'ldap',
uri,
mode,
priority,
timeout,
};
// Mode-specific options
if (mode === 'direct') {
config.userdn = params.get('userdn');
} else {
config.basedn = params.get('basedn');
config.binddn = params.get('binddn');
config.bindpass = params.get('bindpass');
config.userfilter = params.get('userfilter');
config.groupdn = params.get('groupdn');
}
// Optional for both modes
config.accountattr = params.get('accountattr');
return config;
}
/**
* Parse Keycloak auth provider config
*/
function parseKeycloakAuthConfig(params: Map<string, string>): KeycloakAuthConfig | null {
const url = params.get('url');
if (!url) {
console.error('Keycloak auth provider requires url');
return null;
}
const realm = params.get('realm');
if (!realm) {
console.error('Keycloak auth provider requires realm');
return null;
}
const clientid = params.get('clientid');
if (!clientid) {
console.error('Keycloak auth provider requires clientid');
return null;
}
const priority = params.has('priority')
? parseInt(params.get('priority')!, 10)
: undefined;
const timeout = params.has('timeout')
? parseInt(params.get('timeout')!, 10)
: undefined;
return {
provider: 'keycloak',
url,
realm,
clientid,
clientsecret: params.get('clientsecret'),
accountattr: params.get('accountattr'),
timeout,
priority,
};
}
/**
* Convert legacy SASLDB directive to file provider config
*/
export function convertSASLDB(path: string): FileAuthConfig {
return {
provider: 'file',
path,
priority: 100, // Default priority
};
}