Changes
7 changed files (+475/-0)
-
auth/README.md (new)
-
@@ -0,0 +1,19 @@<!-- SPDX-License-Identifier: AGPL-3.0-only Copyright 2026 Shota FUJI This program 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. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. --> # auth アクセス認可用の WebAssembly に関するモジュール。
-
-
auth/build.zig (new)
-
@@ -0,0 +1,108 @@// SPDX-License-Identifier: AGPL-3.0-only // Copyright 2026 Shota FUJI // // This program 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. // // This program 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 this program. If not, see <http://www.gnu.org/licenses/>. const std = @import("std"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); // Default install { const wasm = b.addExecutable(.{ .name = "yamori_default_auth", .root_module = createMainModule( b, b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), optimize, ), }); wasm.entry = .disabled; b.installArtifact(wasm); } // "zig build test" { const t = b.addTest(.{ .name = "yamori_default_auth_test", .root_module = createMainModule(b, target, optimize), }); const run = b.addRunArtifact(t); const step = b.step("test", "Run unit tests"); step.dependOn(&run.step); } } fn createMainModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { const flatbuffers = b.dependency("flatbuffers", .{}); const bfbs = bfbs: { const flatc = b.addSystemCommand(&.{ "flatc", "-b", "--schema", "--bfbs-comments", "--bfbs-builtins", }); flatc.addArg("-o"); const dir = flatc.addOutputDirectoryArg("out"); flatc.addFileArg(b.path("v1.fbs")); break :bfbs dir.path(b, "v1.bfbs"); }; const binding = binding: { const parser = b.addExecutable(.{ .name = "zfbs-parse", .root_module = flatbuffers.module("parse"), }); const parse = b.addRunArtifact(parser); parse.addFileArg(bfbs); const zon = parse.captureStdOut(.{ .basename = "v1.zon" }); const generator = b.addExecutable(.{ .name = "zfbs-generate", .root_module = flatbuffers.module("generate"), }); const generate = b.addRunArtifact(generator); generate.addFileArg(zon); const source = generate.captureStdOut(.{ .basename = "v1.zig" }); const dir = b.addWriteFiles(); _ = dir.addCopyFile(zon, "v1.zon"); const root = dir.addCopyFile(source, "v1.zig"); break :binding b.createModule(.{ .root_source_file = root, .imports = &.{.{ .name = "flatbuffers", .module = flatbuffers.module("flatbuffers") }}, }); }; const mod = b.createModule(.{ .root_source_file = b.path("default_auth.zig"), .optimize = optimize, .target = target, }); mod.export_symbol_names = &.{"v1_allow_resource_access"}; mod.addImport("flatbuffers", flatbuffers.module("flatbuffers")); mod.addImport("v1", binding); return mod; }
-
-
auth/build.zig.zon (new)
-
@@ -0,0 +1,26 @@// SPDX-License-Identifier: AGPL-3.0-only // Copyright 2026 Shota FUJI // // This program 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. // // This program 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 this program. If not, see <http://www.gnu.org/licenses/>. .{ .name = .auth, .version = "0.16.0", .dependencies = .{ .flatbuffers = .{ .url = "https://github.com/nDimensional/zig-flatbuffers/archive/refs/tags/v0.2.1.tar.gz", .hash = "flatbuffers-0.2.1-Ntd6Dj4tAgAaAnjU4WN1nBvxfeZ0yHDW1JNCNr1i1EKB", }, }, .paths = .{""}, .fingerprint = 0xf8deb059a70557a2, }
-
-
auth/default.nix (new)
-
@@ -0,0 +1,86 @@# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026 Shota FUJI # # This program 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. # # This program 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 this program. If not, see <http://www.gnu.org/licenses/>. { stdenvNoCC, lib, flatbuffers, zig_0_16, runCommand, }: stdenvNoCC.mkDerivation { name = "yamori-default-auth"; nativeBuildInputs = [ zig_0_16 flatbuffers ]; src = with lib.fileset; toSource { root = ./.; fileset = unions [ ./v1.fbs ./default_auth.zig ./build.zig ./build.zig.zon ]; }; # nixpkgs の Zig ビルド hook が "standardTargetOptions" や "standardOptimizeOption" # で追加されるオプションをハードコードしてしまっているが、このモジュールは WASM # しか出力しない。 dontSetZigDefaultFlags = true; zigBuildFlags = [ "-Doptimize=ReleaseSmall" ]; postConfigure = let # nixpkgs の zig.fetchDeps は v0.16 に対応していない。 # この関数はそれを v0.16 に対応させたもの。 deps = runCommand "yamori-auth-fetch-deps" { src = with lib.fileset; toSource { root = ./.; fileset = unions [ ./build.zig ./build.zig.zon ]; }; nativeBuildInputs = [ zig_0_16 ]; outputHashAlgo = null; outputHashMode = "recursive"; outputHash = "sha256-D7CU4yXg9M6sa8k2IMjykQD+XItc5moSTHdmk5CCmEQ="; } '' export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d) runHook unpackPhase cd $sourceRoot zig build --fetch=all mv zig-pkg $out ''; in '' ln -s ${deps} zig-pkg ''; }
-
-
auth/default_auth.zig (new)
-
@@ -0,0 +1,135 @@// SPDX-License-Identifier: AGPL-3.0-only // Copyright 2026 Shota FUJI // // This program 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. // // This program 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 this program. If not, see <http://www.gnu.org/licenses/>. const std = @import("std"); const flatbuffers = @import("flatbuffers"); const v1 = @import("v1"); const schema = v1.Yamori.Auth.V1; /// * 管理者以外は自分のリソースのみ作成・変更可能 /// * どのユーザでも全てのリソースを閲覧可能 export fn v1_allow_resource_access(buffer_ptr: [*]const u8, buffer_len: u32) bool { const buffer = buffer_ptr[0..buffer_len]; return v1AllowResourceAccess(buffer) catch return false; } fn v1AllowResourceAccess(buffer: []const u8) !bool { const root = flatbuffers.decodeRoot(schema.AccessRequest, @alignCast(buffer)) catch return false; if (root.user().is_super_user()) { return true; } const access_type = root.access_type(); if (access_type == .Read) { return true; } return switch (root.resource_type()) { .User => |u| switch (access_type) { .Modify => std.mem.eql(u8, u.id(), root.user().id()), .Read => unreachable, else => false, }, .UserAttribute => false, .UserAttributeDefinition => false, else => false, }; } test "フィールドが欠けている場合はアクセスを拒否する" { var builder = try flatbuffers.Builder.init(std.testing.allocator); defer builder.deinit(); try builder.writeRoot(schema.AccessRequest, try builder.writeTable(schema.AccessRequest, .{ .user = try builder.writeTable(schema.User, .{ .id = "foo-bar", }), })); const buffer = try builder.writeAlloc(std.testing.allocator); defer std.testing.allocator.free(buffer); try std.testing.expectEqual(false, try v1AllowResourceAccess(buffer)); } test "管理者はユーザを追加できる" { var builder = try flatbuffers.Builder.init(std.testing.allocator); defer builder.deinit(); try builder.writeRoot(schema.AccessRequest, try builder.writeTable(schema.AccessRequest, .{ .user = try builder.writeTable(schema.User, .{ .id = "foo-bar", .is_super_user = true, }), .access_type = .Create, .resource_type = .{ .User = try builder.writeTable(schema.User, .{ .id = "baz-qux", .is_super_user = true, }), }, })); const buffer = try builder.writeAlloc(std.testing.allocator); defer std.testing.allocator.free(buffer); try std.testing.expectEqual(true, try v1AllowResourceAccess(buffer)); } test "非管理者は自身の基本情報を変更できる" { var builder = try flatbuffers.Builder.init(std.testing.allocator); defer builder.deinit(); try builder.writeRoot(schema.AccessRequest, try builder.writeTable(schema.AccessRequest, .{ .user = try builder.writeTable(schema.User, .{ .id = "foo-bar", }), .access_type = .Modify, .resource_type = .{ .User = try builder.writeTable(schema.User, .{ .id = "foo-bar", }), }, })); const buffer = try builder.writeAlloc(std.testing.allocator); defer std.testing.allocator.free(buffer); try std.testing.expectEqual(true, try v1AllowResourceAccess(buffer)); } test "非管理者は他人の基本情報を変更できない" { var builder = try flatbuffers.Builder.init(std.testing.allocator); defer builder.deinit(); try builder.writeRoot(schema.AccessRequest, try builder.writeTable(schema.AccessRequest, .{ .user = try builder.writeTable(schema.User, .{ .id = "foo-bar", }), .access_type = .Modify, .resource_type = .{ .User = try builder.writeTable(schema.User, .{ .id = "baz-qux", }), }, })); const buffer = try builder.writeAlloc(std.testing.allocator); defer std.testing.allocator.free(buffer); try std.testing.expectEqual(false, try v1AllowResourceAccess(buffer)); }
-
-
auth/v1.fbs (new)
-
@@ -0,0 +1,99 @@// SPDX-License-Identifier: AGPL-3.0-only // Copyright 2026 Shota FUJI // // This program 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. // // This program 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 this program. If not, see <http://www.gnu.org/licenses/>. namespace Yamori.Auth.V1; /// リソースに対するアクセスの種類 enum ResourceAccessType : uint8 { /// 上書きや削除といった既存のデータに対する変更 Modify, /// 追加・登録 /// /// 既存の同種リソースに対して変更は発生しない。同種の他のリソースに対して /// 変更が必要な場合は `Modify` のリソース変更も同時に認可を行う。 Create, /// 読み取り /// /// 一覧取得といったアクセスは個別のリソースに対してそれぞれ `Read` の /// 認可が行われる。 Read, } /// ワークスペース固有のカスタム属性定義 /// /// この定義を基に各ユーザにカスタム属性が定義できる。表示名の変更や半角・全角問題 /// などに対応するため、自由入力はできず事前に定義された選択肢の中から選ぶ。 /// /// また「ユーザ情報変更はできるが一部のカスタム属性にアクセスできない」といった /// ケースを考慮し、全てのカスタム属性は任意選択とする。 table UserAttributeDefinition { /// ユーザ・組織が設定する、認可や一意性確保のための識別キー /// /// ユーザによって値が変更される場合がある。 key: string (required); } /// ユーザに設定されたカスタム属性 table UserAttribute { /// 該当する `UserAttributeDefinition` の `key` definition_key: string (required); /// 選択肢のキー choice_key: string; } /// システムに登録されているログイン可能なユーザ table User { /// システムによって自動発番された ID /// /// 同一判定にはこのフィールドを利用すること。値は不変である。 id: string (required); /// 管理者かどうか /// /// `attributes` でも表現できる属性ではあるが、ベクターを毎回走査するパフォーマンス /// ペナルティを避けるために設置している。ハンドラ内でまずこのフィールドを確認し、 /// `true` の場合にアクセスを許可すれば「誰もアクセスできないデータがある」状態を /// 避けることができる。 /// /// サーバにあるデータベースファイルにアクセスできる人物は実質全てのアクセス権限を /// 持つに等しい。ただ、意図しない変更を予防する等の理由でアクセスを制限することにも /// 長所があるため、組織のリスクマネジメント方針に基づいて決めることを推奨。 is_super_user: bool; /// ワークスペース固有のカスタム属性一覧 attributes: [UserAttribute]; } union Resource { User, UserAttribute, UserAttributeDefinition, } /// 認可用 WebAssembly 関数に渡されるデータ table AccessRequest { /// アクセスを要求しているユーザ user: User (required); /// アクセスの種類 access_type: ResourceAccessType; /// アクセスしようとしているリソース resource: Resource; } root_type AccessRequest;
-
-
-
@@ -46,6 +46,7 @@rec { packages = forEachSystems ( { pkgs, ... }: { auth-default = pkgs.callPackage ./auth { }; demo = pkgs.callPackage ./demo { }; docs = pkgs.callPackage ./docs { }; }
-
@@ -115,6 +116,7 @@# https://github.com/zigtools/zls zls_0_16 ] ++ packages.${system}.auth-default.nativeBuildInputs ++ packages.${system}.demo.nativeBuildInputs ++ packages.${system}.docs.nativeBuildInputs; };
-