refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -0,0 +1,424 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {ApplicationFlags, BotFlags, BotFlagsDescriptions} from '@fluxer/constants/src/BotConstants';
import {AVATAR_MAX_SIZE} from '@fluxer/constants/src/LimitConstants';
import {
PublicUserFlags,
PublicUserFlagsDescriptions,
UserAuthenticatorTypes,
UserAuthenticatorTypesDescriptions,
} from '@fluxer/constants/src/UserConstants';
import {createBase64StringType} from '@fluxer/schema/src/primitives/FileValidators';
import {
createBitflagInt32Type,
createInt32EnumType,
createNamedStringLiteralUnion,
createStringType,
Int32Type,
SnowflakeStringType,
SnowflakeType,
withOpenApiType,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {DiscriminatorType, UsernameType} from '@fluxer/schema/src/primitives/UserValidators';
import {z} from 'zod';
const RedirectURIString = createStringType(1).refine((value) => {
try {
const u = new URL(value);
return !!u.protocol && !!u.host;
} catch {
return false;
}
}, 'Invalid URL format');
export const OAuthScopes = ['identify', 'email', 'guilds', 'bot'] as const;
export type OAuthScope = (typeof OAuthScopes)[number];
const AuthenticatorTypeEnum = withOpenApiType(
createInt32EnumType(
[
[UserAuthenticatorTypes.TOTP, 'TOTP', UserAuthenticatorTypesDescriptions.TOTP],
[UserAuthenticatorTypes.SMS, 'SMS', UserAuthenticatorTypesDescriptions.SMS],
[UserAuthenticatorTypes.WEBAUTHN, 'WEBAUTHN', UserAuthenticatorTypesDescriptions.WEBAUTHN],
],
'The type of authenticator',
'AuthenticatorType',
),
'AuthenticatorType',
);
const PromptType = withOpenApiType(
createNamedStringLiteralUnion(
[
['consent', 'CONSENT', 'Always prompt the user for consent'],
['none', 'NONE', 'Do not prompt the user for consent if already authorized'],
] as const,
'Whether to prompt the user for consent',
),
'PromptType',
);
const DisableGuildSelectType = withOpenApiType(
createNamedStringLiteralUnion(
[
['true', 'TRUE', 'Disable guild selection'],
['false', 'FALSE', 'Allow guild selection'],
] as const,
'Whether to disable guild selection',
),
'DisableGuildSelectType',
);
export const AuthorizeRequest = z.object({
response_type: z.literal('code').optional().describe('The OAuth2 response type, must be "code"'),
client_id: SnowflakeType.describe('The application client ID'),
redirect_uri: RedirectURIString.optional().describe('The URI to redirect to after authorization'),
scope: createStringType(1).describe('The space-separated list of requested scopes'),
state: createStringType(1).optional().describe('A random string for CSRF protection'),
prompt: PromptType.optional(),
guild_id: SnowflakeType.optional().describe('The guild ID to pre-select for bot authorization'),
permissions: z.string().optional().describe('The bot permissions to request'),
disable_guild_select: DisableGuildSelectType.optional(),
});
export type AuthorizeRequest = z.infer<typeof AuthorizeRequest>;
export const AuthorizeConsentRequest = z.object({
response_type: z.string().optional().describe('The OAuth2 response type'),
client_id: SnowflakeType.describe('The application client ID'),
redirect_uri: RedirectURIString.optional().describe('The URI to redirect to after authorization'),
scope: createStringType(1).describe('The space-separated list of requested scopes'),
state: createStringType(1).optional().describe('A random string for CSRF protection'),
permissions: z.string().optional().describe('The bot permissions to request'),
guild_id: SnowflakeType.optional().describe('The guild ID to add the bot to'),
});
export type AuthorizeConsentRequest = z.infer<typeof AuthorizeConsentRequest>;
export const TokenRequest = z.discriminatedUnion('grant_type', [
z.object({
grant_type: z.literal('authorization_code').describe('The grant type for exchanging an authorization code'),
code: createStringType(1).describe('The authorization code received from the authorize endpoint'),
redirect_uri: RedirectURIString.describe('The redirect URI used in the authorization request'),
client_id: SnowflakeType.optional().describe('The application client ID'),
client_secret: createStringType(1).optional().describe('The application client secret'),
}),
z.object({
grant_type: z.literal('refresh_token').describe('The grant type for refreshing an access token'),
refresh_token: createStringType(1).describe('The refresh token to exchange for a new access token'),
client_id: SnowflakeType.optional().describe('The application client ID'),
client_secret: createStringType(1).optional().describe('The application client secret'),
}),
]);
export type TokenRequest = z.infer<typeof TokenRequest>;
export const IntrospectRequestForm = z.object({
token: createStringType(1).describe('The token to introspect'),
client_id: SnowflakeType.optional().describe('The application client ID'),
client_secret: createStringType(1).optional().describe('The application client secret'),
});
export type IntrospectRequestForm = z.infer<typeof IntrospectRequestForm>;
const TokenTypeHint = withOpenApiType(
createNamedStringLiteralUnion(
[
['access_token', 'ACCESS_TOKEN', 'An OAuth2 access token'],
['refresh_token', 'REFRESH_TOKEN', 'An OAuth2 refresh token'],
] as const,
'A hint about the type of token being revoked',
),
'TokenTypeHint',
);
export const RevokeRequestForm = z.object({
token: createStringType(1).describe('The token to revoke'),
token_type_hint: TokenTypeHint.optional(),
client_id: SnowflakeType.optional().describe('The application client ID'),
client_secret: createStringType(1).optional().describe('The application client secret'),
});
export type RevokeRequestForm = z.infer<typeof RevokeRequestForm>;
export const ApplicationBotResponse = z
.object({
id: SnowflakeStringType.describe('The unique identifier of the bot user'),
username: z.string().describe('The username of the bot'),
discriminator: z.string().describe('The discriminator of the bot'),
avatar: z.string().nullable().optional().describe('The avatar hash of the bot'),
banner: z.string().nullable().optional().describe('The banner hash of the bot'),
bio: z.string().nullable().describe('The bio or description of the bot'),
token: z.string().optional().describe('The bot token for authentication'),
mfa_enabled: z.boolean().optional().describe('Whether the bot has MFA enabled'),
authenticator_types: z
.array(AuthenticatorTypeEnum)
.max(10)
.optional()
.describe('The types of authenticators enabled'),
flags: createBitflagInt32Type(BotFlags, BotFlagsDescriptions, 'The bot user flags', 'BotFlags'),
})
.describe('Detailed bot user metadata');
export type ApplicationBotResponse = z.infer<typeof ApplicationBotResponse>;
export const ApplicationResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier of the application'),
name: z.string().describe('The name of the application'),
redirect_uris: z.array(z.string()).max(20).describe('The registered redirect URIs for OAuth2'),
bot_public: z.boolean().describe('Whether the bot can be invited by anyone'),
bot_require_code_grant: z.boolean().describe('Whether the bot requires OAuth2 code grant'),
client_secret: z.string().optional().describe('The client secret for OAuth2 authentication'),
bot: ApplicationBotResponse.optional().describe('The bot user associated with the application'),
});
export type ApplicationResponse = z.infer<typeof ApplicationResponse>;
export const ApplicationListResponse = z.array(ApplicationResponse);
export type ApplicationListResponse = z.infer<typeof ApplicationListResponse>;
export const BotTokenResetResponse = z.object({
token: z.string().describe('The new bot token'),
bot: ApplicationBotResponse,
});
export type BotTokenResetResponse = z.infer<typeof BotTokenResetResponse>;
export const BotProfileResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier of the bot user'),
username: z.string().describe('The username of the bot'),
discriminator: z.string().describe('The discriminator of the bot'),
avatar: z.string().nullable().describe('The avatar hash of the bot'),
banner: z.string().nullable().describe('The banner hash of the bot'),
bio: z.string().nullable().describe('The bio or description of the bot'),
});
export type BotProfileResponse = z.infer<typeof BotProfileResponse>;
export const OAuth2TokenResponse = z.object({
access_token: z.string().describe('The access token for API authorization'),
token_type: z.string().describe('The type of token, typically "Bearer"'),
expires_in: Int32Type.describe('The number of seconds until the access token expires'),
refresh_token: z.string().describe('The refresh token for obtaining new access tokens'),
scope: z.string().describe('The space-separated list of granted scopes'),
});
export type OAuth2TokenResponse = z.infer<typeof OAuth2TokenResponse>;
export const OAuth2UserInfoResponse = z.object({
sub: SnowflakeStringType.describe('The subject identifier of the user'),
id: SnowflakeStringType.describe('The unique identifier of the user'),
username: z.string().describe('The username of the user'),
discriminator: z.string().describe('The discriminator of the user'),
global_name: z.string().nullable().describe('The global display name of the user'),
avatar: z.string().nullable().describe('The avatar hash of the user'),
email: z.string().nullable().optional().describe('The email address of the user'),
verified: z.boolean().nullable().optional().describe('Whether the user has verified their email'),
flags: createBitflagInt32Type(
PublicUserFlags,
PublicUserFlagsDescriptions,
'The user flags',
'PublicUserFlags',
).optional(),
});
export type OAuth2UserInfoResponse = z.infer<typeof OAuth2UserInfoResponse>;
export const OAuth2IntrospectResponse = z.object({
active: z.boolean().describe('Whether the token is currently active'),
scope: z.string().optional().describe('The space-separated list of scopes'),
client_id: SnowflakeStringType.optional().describe('The client identifier for the token'),
username: z.string().optional().describe('The username of the token owner'),
token_type: z.string().optional().describe('The type of token'),
exp: Int32Type.optional().describe('The expiration timestamp in seconds'),
iat: Int32Type.optional().describe('The issued-at timestamp in seconds'),
sub: SnowflakeStringType.optional().describe('The subject identifier (user ID)'),
});
export type OAuth2IntrospectResponse = z.infer<typeof OAuth2IntrospectResponse>;
export const OAuth2ConsentResponse = z.object({
redirect_to: z.string().describe('The URL to redirect the user to after consent'),
});
export type OAuth2ConsentResponse = z.infer<typeof OAuth2ConsentResponse>;
export const OAuth2MeResponse = z.object({
application: z
.object({
id: SnowflakeStringType.describe('The unique identifier of the application'),
name: z.string().describe('The name of the application'),
icon: z.null().describe('The icon hash of the application'),
description: z.null().describe('The description of the application'),
bot_public: z.boolean().describe('Whether the bot can be invited by anyone'),
bot_require_code_grant: z.boolean().describe('Whether the bot requires OAuth2 code grant'),
flags: createBitflagInt32Type(ApplicationFlags, 'The application flags', undefined, 'ApplicationFlags'),
})
.describe('The application associated with the token'),
scopes: z.array(z.string()).max(50).describe('The list of granted OAuth2 scopes'),
expires: z.string().describe('The expiration timestamp of the token'),
user: z
.object({
id: SnowflakeStringType.describe('The unique identifier of the user'),
username: z.string().describe('The username of the user'),
discriminator: z.string().describe('The discriminator of the user'),
global_name: z.string().nullable().describe('The global display name of the user'),
avatar: z.string().nullable().describe('The avatar hash of the user'),
avatar_color: Int32Type.nullable().describe('The default avatar color of the user'),
bot: z.boolean().optional().describe('Whether the user is a bot'),
system: z.boolean().optional().describe('Whether the user is a system user'),
flags: createBitflagInt32Type(PublicUserFlags, PublicUserFlagsDescriptions, 'The user flags', 'PublicUserFlags'),
email: z.string().nullable().optional().describe('The email address of the user'),
verified: z.boolean().nullable().optional().describe('Whether the user has verified their email'),
})
.optional()
.describe('The user associated with the token'),
});
export type OAuth2MeResponse = z.infer<typeof OAuth2MeResponse>;
export const ApplicationPublicResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier of the application'),
name: z.string().describe('The name of the application'),
icon: z.string().nullable().describe('The icon hash of the application'),
description: z.null().describe('The description of the application'),
redirect_uris: z.array(z.string()).max(20).describe('The registered redirect URIs for OAuth2'),
scopes: z.array(z.string()).max(50).describe('The available OAuth2 scopes'),
bot_public: z.boolean().describe('Whether the bot can be invited by anyone'),
bot: ApplicationBotResponse.nullable().describe('The bot user associated with the application'),
});
export type ApplicationPublicResponse = z.infer<typeof ApplicationPublicResponse>;
export const ApplicationsMeResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier of the application'),
name: z.string().describe('The name of the application'),
icon: z.null().describe('The icon hash of the application'),
description: z.null().describe('The description of the application'),
bot_public: z.boolean().describe('Whether the bot can be invited by anyone'),
bot_require_code_grant: z.boolean().describe('Whether the bot requires OAuth2 code grant'),
flags: createBitflagInt32Type(ApplicationFlags, 'The application flags', undefined, 'ApplicationFlags'),
bot: ApplicationBotResponse.optional().describe('The bot user associated with the application'),
});
export type ApplicationsMeResponse = z.infer<typeof ApplicationsMeResponse>;
export const OAuth2AuthorizationResponse = z.object({
application: z
.object({
id: SnowflakeStringType.describe('The unique identifier of the application'),
name: z.string().describe('The name of the application'),
icon: z.string().nullable().describe('The icon hash of the application'),
description: z.null().describe('The description of the application'),
bot_public: z.boolean().describe('Whether the bot can be invited by anyone'),
})
.describe('The application that was authorized'),
scopes: z.array(z.string()).max(50).describe('The list of granted OAuth2 scopes'),
authorized_at: z.string().describe('The timestamp when the authorization was granted'),
});
export type OAuth2AuthorizationResponse = z.infer<typeof OAuth2AuthorizationResponse>;
export const OAuth2AuthorizationsListResponse = z.array(OAuth2AuthorizationResponse);
export type OAuth2AuthorizationsListResponse = z.infer<typeof OAuth2AuthorizationsListResponse>;
function isLoopbackHost(hostname: string) {
const lowercaseHost = hostname.toLowerCase();
return (
lowercaseHost === 'localhost' ||
lowercaseHost === '127.0.0.1' ||
lowercaseHost === '[::1]' ||
lowercaseHost.endsWith('.localhost')
);
}
function isValidRedirectURI(value: string, allowAnyHttp: boolean) {
try {
const url = new URL(value);
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
return false;
}
if (!allowAnyHttp && url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
return false;
}
return !!url.host;
} catch {
return false;
}
}
const createRedirectURIType = (allowAnyHttp: boolean, message: string) =>
createStringType(1).refine((value) => isValidRedirectURI(value, allowAnyHttp), message);
export const OAuth2RedirectURICreateType = createRedirectURIType(
false,
'Redirect URIs must use HTTPS, or HTTP for localhost only',
);
export const OAuth2RedirectURIUpdateType = createRedirectURIType(true, 'Redirect URIs must use HTTP or HTTPS');
export const ApplicationCreateRequest = z.object({
name: createStringType(1, 100).describe('The name of the application'),
redirect_uris: z
.array(OAuth2RedirectURICreateType)
.max(10, 'Maximum of 10 redirect URIs allowed')
.optional()
.nullable()
.transform((value) => value ?? [])
.describe('The redirect URIs for OAuth2 flows'),
bot_public: z.boolean().optional().describe('Whether the bot can be invited by anyone'),
bot_require_code_grant: z.boolean().optional().describe('Whether the bot requires OAuth2 code grant'),
});
export type ApplicationCreateRequest = z.infer<typeof ApplicationCreateRequest>;
export const ApplicationUpdateRequest = z.object({
name: createStringType(1, 100).optional().describe('The name of the application'),
redirect_uris: z
.array(OAuth2RedirectURIUpdateType)
.max(10, 'Maximum of 10 redirect URIs allowed')
.optional()
.nullable()
.transform((value) => (value === undefined ? undefined : (value ?? [])))
.describe('The redirect URIs for OAuth2 flows'),
bot_public: z.boolean().optional().describe('Whether the bot can be invited by anyone'),
bot_require_code_grant: z.boolean().optional().describe('Whether the bot requires OAuth2 code grant'),
});
export type ApplicationUpdateRequest = z.infer<typeof ApplicationUpdateRequest>;
export const BotProfileUpdateRequest = z.object({
username: UsernameType.optional().describe('The username of the bot'),
discriminator: DiscriminatorType.optional().describe('The discriminator of the bot'),
avatar: createBase64StringType(1, Math.ceil(AVATAR_MAX_SIZE * (4 / 3)))
.nullish()
.describe('The avatar image as base64'),
banner: createBase64StringType(1, Math.ceil(AVATAR_MAX_SIZE * (4 / 3)))
.nullish()
.describe('The banner image as base64'),
bio: createStringType(0, 1024).nullish().describe('The bio or description of the bot'),
bot_flags: createBitflagInt32Type(BotFlags, BotFlagsDescriptions, 'The bot user flags', 'BotFlags').optional(),
});
export type BotProfileUpdateRequest = z.infer<typeof BotProfileUpdateRequest>;