-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from amphineko/feat-eap-password
EAP-PEAP-GTC/MSCHAPv2 with local SQLite password backend
- Loading branch information
Showing
30 changed files
with
1,218 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as t from "io-ts" | ||
|
||
import { RadiusUserPasswordStatusType, RadiusUserPasswords, RadiusUserType } from "../types/users/RadiusUser" | ||
|
||
export const CreateOrUpdateUserRequestType = t.partial({}) | ||
|
||
export type CreateOrUpdateUserRequest = t.TypeOf<typeof CreateOrUpdateUserRequestType> | ||
|
||
export const ListUserResponseType = t.readonlyArray(RadiusUserType) | ||
|
||
export type ListUserResponse = t.TypeOf<typeof ListUserResponseType> | ||
|
||
export const ListUserPasswordStatusResponseType = t.readonlyArray(RadiusUserPasswordStatusType) | ||
|
||
export const UpdateUserPasswordsRequestType: t.Type<Partial<RadiusUserPasswords>> = t.partial({ | ||
clearText: t.union([t.string, t.null]), | ||
ntHash: t.union([t.string, t.null]), | ||
ssha: t.union([t.string, t.null]), | ||
ssha512: t.union([t.string, t.null]), | ||
}) | ||
|
||
export type UpdateUserPasswordsRequest = t.TypeOf<typeof UpdateUserPasswordsRequestType> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import * as t from "io-ts" | ||
|
||
import { Username, UsernameType } from "./Username" | ||
|
||
export interface RadiusUser { | ||
username: Username | ||
} | ||
|
||
interface EncodedRadiusUser { | ||
username: string | ||
} | ||
|
||
export const RadiusUserType: t.Type<RadiusUser, EncodedRadiusUser> = t.type({ | ||
username: UsernameType, | ||
}) | ||
|
||
/** | ||
* For each password attribute, | ||
* string = password hash for this method | ||
* null = method is disabled | ||
*/ | ||
export interface RadiusUserPasswords { | ||
username: Username | ||
clearText: string | null | ||
ntHash: string | null | ||
ssha512: string | null | ||
} | ||
|
||
interface EncodedRadiusUserPasswords { | ||
username: string | ||
clearText: string | null | ||
ntHash: string | null | ||
ssha512: string | null | ||
} | ||
|
||
export const RadiusUserPasswordsType: t.Type<RadiusUserPasswords, EncodedRadiusUserPasswords> = t.type({ | ||
username: UsernameType, | ||
clearText: t.union([t.string, t.null]), | ||
ntHash: t.union([t.string, t.null]), | ||
ssha512: t.union([t.string, t.null]), | ||
}) | ||
|
||
/** | ||
* Indicates whether a user has a password set for each method | ||
*/ | ||
export type RadiusUserPasswordStatus = { | ||
[K in keyof RadiusUserPasswords]: K extends "username" ? Username : boolean | ||
} | ||
|
||
type EncodedRadiusUserPasswordStatus = { | ||
[K in keyof RadiusUserPasswordStatus]: RadiusUserPasswords[K] extends Username ? string : boolean | ||
} | ||
|
||
export const RadiusUserPasswordStatusType: t.Type<RadiusUserPasswordStatus, EncodedRadiusUserPasswordStatus> = t.type({ | ||
username: UsernameType, | ||
clearText: t.boolean, | ||
ntHash: t.boolean, | ||
ssha512: t.boolean, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as E from "fp-ts/lib/Either" | ||
import * as F from "fp-ts/lib/function" | ||
import * as t from "io-ts" | ||
|
||
const MAX_USERNAME_LENGTH = 64 | ||
|
||
const USERNAME_REGEX = /^[a-zA-Z0-9_-]+$/ | ||
|
||
function isUsername(u: string): boolean { | ||
return u.length > 0 && u.length <= MAX_USERNAME_LENGTH && USERNAME_REGEX.test(u) | ||
} | ||
|
||
export type Username = t.Branded<string, { readonly Username: unique symbol }> | ||
|
||
export const UsernameType = new t.Type<Username, string, unknown>( | ||
"Username", | ||
(u): u is Username => typeof u === "string" && isUsername(u), | ||
(u, c) => | ||
F.pipe( | ||
t.string.validate(u, c), | ||
E.chain((u) => (isUsername(u) ? t.success(u as Username) : t.failure(u, c))), | ||
), | ||
t.identity, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { | ||
BadRequestException, | ||
Body, | ||
Controller, | ||
Delete, | ||
Get, | ||
Inject, | ||
Param, | ||
Post, | ||
UseInterceptors, | ||
} from "@nestjs/common" | ||
import { | ||
CreateOrUpdateUserRequestType, | ||
ListUserPasswordStatusResponseType, | ||
ListUserResponse, | ||
UpdateUserPasswordsRequestType, | ||
} from "@yonagi/common/api/users" | ||
import { RadiusUserPasswordStatus } from "@yonagi/common/types/users/RadiusUser" | ||
import { UsernameType } from "@yonagi/common/types/users/Username" | ||
import { mapValidationLeftError } from "@yonagi/common/utils/Either" | ||
import { getOrThrow, tryCatchF } from "@yonagi/common/utils/TaskEither" | ||
import * as E from "fp-ts/lib/Either" | ||
import * as TE from "fp-ts/lib/TaskEither" | ||
import * as F from "fp-ts/lib/function" | ||
|
||
import { ResponseInterceptor } from "./api.middleware" | ||
import { EncodeResponseWith } from "./common" | ||
import { AbstractRadiusUserPasswordStorage, AbstractRadiusUserStorage } from "../storages" | ||
|
||
@Controller("/api/v1/users") | ||
@UseInterceptors(ResponseInterceptor) | ||
export class RadiusUserController { | ||
@Get("/") | ||
async all(): Promise<ListUserResponse> { | ||
return await this.storage.all() | ||
} | ||
|
||
@Post("/:username") | ||
async createOrUpdate(@Param("username") unknownUsername: unknown, @Body() u: unknown): Promise<void> { | ||
await F.pipe( | ||
TE.fromEither( | ||
F.pipe( | ||
E.Do, | ||
E.bindW("username", () => UsernameType.decode(unknownUsername)), | ||
E.bindW("form", () => CreateOrUpdateUserRequestType.decode(u)), | ||
mapValidationLeftError((e) => new BadRequestException(String(e))), | ||
), | ||
), | ||
tryCatchF( | ||
({ username, form }) => this.storage.createOrUpdate(username, { username, ...form }), | ||
(reason) => new Error(String(reason)), | ||
), | ||
getOrThrow(), | ||
)() | ||
} | ||
|
||
@Delete("/:username") | ||
async delete(@Param("username") unknownUsername: unknown): Promise<void> { | ||
await F.pipe( | ||
TE.fromEither( | ||
F.pipe( | ||
UsernameType.decode(unknownUsername), | ||
mapValidationLeftError((e) => new Error(String(e))), | ||
), | ||
), | ||
tryCatchF( | ||
(username) => this.storage.deleteByUsername(username), | ||
(reason) => new Error(String(reason)), | ||
), | ||
getOrThrow(), | ||
)() | ||
} | ||
|
||
constructor(@Inject(AbstractRadiusUserStorage) private readonly storage: AbstractRadiusUserStorage) {} | ||
} | ||
|
||
@Controller("/api/v1/passwords") | ||
@UseInterceptors(ResponseInterceptor) | ||
export class RadiusUserPasswordController { | ||
@Get("/") | ||
@EncodeResponseWith(ListUserPasswordStatusResponseType) | ||
async all(): Promise<readonly RadiusUserPasswordStatus[]> { | ||
return await this.storage.allStatus() | ||
} | ||
|
||
@Post("/:username") | ||
async update(@Param("username") unknownUsername: unknown, @Body() u: unknown): Promise<void> { | ||
await F.pipe( | ||
TE.fromEither( | ||
F.pipe( | ||
E.Do, | ||
E.bindW("username", () => UsernameType.decode(unknownUsername)), | ||
E.bindW("form", () => UpdateUserPasswordsRequestType.decode(u)), | ||
mapValidationLeftError((e) => new BadRequestException(String(e))), | ||
), | ||
), | ||
tryCatchF( | ||
({ username, form }) => this.storage.createOrUpdate(username, form), | ||
(reason) => new Error(String(reason)), | ||
), | ||
getOrThrow(), | ||
)() | ||
} | ||
|
||
constructor( | ||
@Inject(AbstractRadiusUserPasswordStorage) private readonly storage: AbstractRadiusUserPasswordStorage, | ||
) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,14 @@ | ||
import { generateClients } from "./clients" | ||
import { generateEapModule } from "./modules/eapModule" | ||
import { generateRestModule } from "./modules/restModule" | ||
import { generateModules } from "./modules" | ||
import { patchAuthLogEnable } from "./radiusdConfig" | ||
import { generateDefaultSite } from "./sites/defaultSite" | ||
import { generateDynamicClientSite } from "./sites/dynClientSite" | ||
import { generateSites } from "./sites" | ||
import { RaddbGenParams } from ".." | ||
|
||
export async function generateRaddb(params: RaddbGenParams): Promise<void> { | ||
await Promise.all([ | ||
generateClients(params), | ||
generateDynamicClientSite(params), | ||
generateDefaultSite(params), | ||
generateEapModule(params), | ||
generateRestModule(params), | ||
generateModules(params), | ||
generateSites(params), | ||
patchAuthLogEnable(params), | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { generateEapModule } from "./eap" | ||
import { generateMschapModule } from "./mschap" | ||
import { generateRestModule } from "./rest" | ||
import { RaddbGenParams } from "../.." | ||
|
||
export async function generateModules(params: RaddbGenParams): Promise<void> { | ||
await Promise.all([generateEapModule(params), generateMschapModule(params), generateRestModule(params)]) | ||
} |
Oops, something went wrong.