├── .github └── workflows │ └── dev.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api ├── auth │ ├── auth.pb.go │ ├── auth.pb.validate.go │ ├── auth_grpc.pb.go │ └── auth_http.pb.go └── reason │ ├── reason.pb.go │ ├── reason.pb.validate.go │ └── reason_errors.pb.go ├── cmd └── auth │ ├── main.go │ ├── wire.go │ └── wire_gen.go ├── configs ├── config.yml ├── gen.yml ├── hotspot.yml ├── log.yml ├── task.yml └── tracer.yml ├── docs └── auth-proto │ └── auth.openapi.yaml ├── go.mod ├── go.sum ├── internal ├── biz │ ├── README.md │ ├── action.go │ ├── biz.go │ ├── hotspot.go │ ├── permission.go │ ├── reason.go │ ├── role.go │ ├── user.go │ ├── user_group.go │ └── whitelist.go ├── conf │ ├── conf.pb.go │ └── conf.proto ├── data │ ├── README.md │ ├── action.go │ ├── cache.go │ ├── data.go │ ├── hotspot.go │ ├── model │ │ ├── action.gen.go │ │ ├── role.gen.go │ │ ├── user.gen.go │ │ ├── user_group.gen.go │ │ ├── user_user_group_relation.gen.go │ │ └── whitelist.gen.go │ ├── permission.go │ ├── query │ │ ├── action.gen.go │ │ ├── gen.go │ │ ├── role.gen.go │ │ ├── user.gen.go │ │ ├── user_group.gen.go │ │ ├── user_user_group_relation.gen.go │ │ └── whitelist.gen.go │ ├── role.go │ ├── tracer.go │ ├── user.go │ ├── user_group.go │ └── whitelist.go ├── db │ ├── db.go │ └── migrations │ │ ├── 2022092814-user.sql │ │ ├── 2022101016-action.sql │ │ ├── 2022101017-role.sql │ │ ├── 2022101114-user-group.sql │ │ ├── 2023060210-whitelist.sql │ │ └── 2023060217-default-data.sql ├── pkg │ └── task │ │ └── task.go ├── server │ ├── grpc.go │ ├── health.go │ ├── http.go │ ├── middleware │ │ ├── header.go │ │ ├── idempotent.go │ │ ├── locales │ │ │ ├── en.yml │ │ │ └── zh.yml │ │ └── permission.go │ └── server.go └── service │ ├── README.md │ ├── action.go │ ├── auth.go │ ├── health.go │ ├── hotspot.go │ ├── role.go │ ├── service.go │ ├── user.go │ ├── user_group.go │ └── whitelist.go └── third_party ├── README.md ├── cinch └── params │ └── params.proto ├── errors └── errors.proto ├── google ├── api │ ├── annotations.proto │ ├── client.proto │ ├── field_behavior.proto │ ├── http.proto │ └── httpbody.proto └── protobuf │ ├── any.proto │ ├── api.proto │ ├── compiler │ └── plugin.proto │ ├── descriptor.proto │ ├── duration.proto │ ├── empty.proto │ ├── field_mask.proto │ ├── source_context.proto │ ├── struct.proto │ ├── timestamp.proto │ ├── type.proto │ └── wrappers.proto ├── openapiv3 ├── OpenAPIv3.proto └── annotations.proto └── validate ├── README.md └── validate.proto /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "dev" ] 6 | pull_request: 7 | branches: [ "dev" ] 8 | 9 | jobs: 10 | 11 | build_and_push: 12 | name: Build and Push Docker image 13 | runs-on: ubuntu-latest 14 | permissions: 15 | packages: write 16 | contents: read 17 | steps: 18 | - name: Check out the repo 19 | uses: actions/checkout@v4 20 | 21 | - name: Build and publish a Docker image for ${{ github.repository }} 22 | uses: macbre/push-to-ghcr@master 23 | with: 24 | image_name: ${{ github.repository }} # it will be lowercased internally 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | dockerfile: ./Dockerfile 27 | image_tag: latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Reference https://github.com/github/gitignore/blob/master/Go.gitignore 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | vendor/ 17 | 18 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 19 | *.o 20 | *.a 21 | *.so 22 | 23 | # OS General 24 | Thumbs.db 25 | .DS_Store 26 | 27 | # project 28 | *.cert 29 | *.key 30 | *.log 31 | bin/ 32 | 33 | # Develop tools 34 | .vscode/ 35 | .idea/ 36 | *.swp 37 | 38 | docs/*/* 39 | !docs/auth-proto/auth.openapi.yaml 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api/auth-proto"] 2 | path = api/auth-proto 3 | url = https://github.com/go-cinch/auth-proto.git 4 | branch = main 5 | [submodule "api/reason-proto"] 6 | path = api/reason-proto 7 | url = https://github.com/go-cinch/reason-proto.git 8 | branch = main 9 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | go: '1.22' 3 | timeout: '5m' 4 | 5 | issues: 6 | exclude-files: 7 | - 'internal/server/grpc.go' 8 | - 'internal/server/http.go' 9 | - 'internal/service/service.go' 10 | 11 | linters: 12 | enable: 13 | - gci 14 | - revive 15 | disable-all: true 16 | 17 | linters-settings: 18 | revive: 19 | rules: 20 | - name: var-naming 21 | arguments: [ [ "ID", "IDS" ] ] 22 | - name: line-length-limit 23 | arguments: [ 200 ] 24 | - name: argument-limit 25 | arguments: [ 10 ] 26 | - name: function-result-limit 27 | arguments: [ 10 ] 28 | - name: blank-imports 29 | - name: duplicated-imports 30 | - name: bool-literal-in-expr 31 | - name: constant-logical-expr 32 | - name: context-as-argument 33 | - name: error-return 34 | - name: deep-exit 35 | - name: defer 36 | - name: early-return 37 | - name: indent-error-flow 38 | - name: if-return 39 | - name: superfluous-else 40 | - name: empty-block 41 | - name: get-return 42 | - name: increment-decrement 43 | - name: modifies-value-receiver 44 | - name: range 45 | - name: range-val-in-closure 46 | - name: receiver-naming 47 | - name: string-of-int 48 | - name: struct-tag 49 | - name: unexported-naming 50 | - name: unexported-return 51 | - name: unreachable-code 52 | - name: unused-parameter 53 | - name: unused-receiver 54 | - name: waitgroup-by-value 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine3.18 AS builder 2 | 3 | #ENV GOPROXY=https://goproxy.cn 4 | # CGO_ENABLED=1, need check `ldd --version` is same as builder 5 | ENV CGO_ENABLED=0 6 | 7 | RUN apk update && apk add --no-cache git make 8 | 9 | # install cinch tool 10 | RUN go install github.com/go-cinch/cinch/cmd/cinch@latest 11 | 12 | COPY . /src 13 | WORKDIR /src 14 | # download first can use docker build cache if go.mod not change 15 | COPY go.mod go.sum ./ 16 | RUN go mod download 17 | RUN go mod verify 18 | 19 | COPY . . 20 | RUN make build 21 | 22 | FROM alpine:3.18 23 | 24 | RUN apk update && apk add --no-cache bash 25 | 26 | COPY --from=builder /src/bin /app 27 | 28 | WORKDIR /app 29 | 30 | COPY configs /data/conf 31 | 32 | CMD ["sh", "-c", "./auth -c /data/conf"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Go Cinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOHOSTOS:=$(shell go env GOHOSTOS) 2 | GOPATH:=$(shell go env GOPATH) 3 | VERSION=$(shell git describe --tags --always) 4 | 5 | ifeq ($(GOHOSTOS), windows) 6 | #the `find.exe` is different from `find` in bash/shell. 7 | #to see https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/find. 8 | #changed to use git-bash.exe to run find cli or other cli friendly, caused of every developer has a Git. 9 | Git_Bash= $(subst cmd\,bin\bash.exe,$(dir $(shell where git))) 10 | INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto") 11 | API_PROTO_FILES=$(shell $(Git_Bash) -c "find api -name *.proto") 12 | else 13 | INTERNAL_PROTO_FILES=$(shell find internal -name *.proto) 14 | API_PROTO_FILES=$(shell find api -maxdepth 2 -name *.proto) 15 | endif 16 | 17 | .PHONY: init 18 | # init env 19 | init: 20 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 21 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 22 | go install github.com/go-kratos/kratos/cmd/kratos/v2@latest 23 | go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest 24 | go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest 25 | go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 26 | go install github.com/envoyproxy/protoc-gen-validate@v1.0.4 27 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.55.2 28 | go install github.com/go-cinch/cinch/cmd/cinch@latest 29 | 30 | .PHONY: config 31 | # generate internal proto 32 | config: 33 | protoc --proto_path=./internal \ 34 | --proto_path=./third_party \ 35 | --go_out=paths=source_relative:./internal \ 36 | $(INTERNAL_PROTO_FILES) 37 | 38 | .PHONY: sub 39 | # update submodule 40 | sub: 41 | git submodule update --force --recursive --init --remote 42 | 43 | .PHONY: api 44 | # generate api proto 45 | api: 46 | @mkdir -p docs 47 | for NAME in $(API_PROTO_FILES); do \ 48 | ROOT=$(shell pwd); \ 49 | DIR=`echo $$NAME | awk -F '-proto/[^/]*$$' '{print $$1}'`; \ 50 | echo $$NAME; \ 51 | protoc --proto_path=./api \ 52 | --proto_path=./third_party \ 53 | --go_out=. \ 54 | --go-errors_out=. \ 55 | --go-http_out=. \ 56 | --go-grpc_out=. \ 57 | --validate_out=lang=go:. \ 58 | --openapi_out=fq_schema_naming=true,default_response=false,output_mode=source_relative:docs \ 59 | $$NAME; \ 60 | done 61 | @echo 'You can import *.json into https://editor.swagger.io/' 62 | 63 | .PHONY: build 64 | # build 65 | build: 66 | mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./... 67 | 68 | .PHONY: gen 69 | # generate wire 70 | gen: 71 | go mod tidy 72 | go get github.com/google/wire/cmd/wire@latest 73 | go generate ./... 74 | go mod tidy 75 | 76 | .PHONY: lint 77 | # golangci-lint 78 | lint: 79 | golangci-lint run --fix 80 | 81 | .PHONY: all 82 | # generate all 83 | all: 84 | make api; 85 | make config; 86 | make gen; 87 | make lint; 88 | 89 | local: 90 | cinch run 91 | 92 | # show help 93 | help: 94 | @echo '' 95 | @echo 'Usage:' 96 | @echo ' make [target]' 97 | @echo '' 98 | @echo 'Targets:' 99 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 100 | helpMessage = match(lastLine, /^# (.*)/); \ 101 | if (helpMessage) { \ 102 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 103 | helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ 104 | printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ 105 | } \ 106 | } \ 107 | { lastLine = $$0 }' $(MAKEFILE_LIST) 108 | 109 | .DEFAULT_GOAL := help 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Go Cinch Auth

2 | 3 |
4 | Auth是一套权限验证微服务. 5 |

6 | Build Status 7 | Go version 8 | Kratos version 9 | MySQL version 10 | Go redis version 11 | Gorm version 12 | Wire version 13 | Go Report Card 14 | License 15 |

16 |
17 | 18 | # 简介 19 | 20 | ## 起源 21 | 22 | 你的单体服务架构是否遇到一些问题, 不能满足业务需求? 那么微服务会是好的解决方案. 23 | Cinch是一套轻量级微服务脚手架, 基于[Kratos], 节省基础服务搭建时间, 快速投入业务开发. 24 | 我们参考了Go的许多微服务架构, 结合实际需求, 最终选择简洁的[Kratos]作为基石(B站架构), 从架构的设计思路以及代码的书写格式和我们非常匹配. 25 | > cinch意为简单的事, 小菜. 希望把复杂的事变得简单, 提升开发效率. 26 | 27 | > 若你想深入学习微服务每个组件, 建议直接看Kratos官方文档. 本项目整合一些业务常用组件, 开箱即用, 并不会把每个组件都介绍那么详细. 28 | 29 | ## 特性 30 | 31 | - `Go` - 最新分支Go版本已更新至`v1.22` 32 | - `Proto` - proto协议同时开启gRPC & HTTP支持, 只需开发一次接口, 不用写两套 33 | - `Jwt` - 认证, 用户登入登出一键搞定 34 | - `Action` - 权限, 基于行为的权限校验 35 | - `Redis` - 缓存, 内置防缓存穿透/缓存击穿/缓存雪崩示例 36 | - `Gorm` - 数据库ORM管理框架, 可自行扩展多种数据库类型, 目前使用MySQL, 其他自行扩展 37 | - `Gorm Gen` - Gorm实体自动生成, 无需再写数据库实体映射 38 | - `Tenant` - 基于Gorm的schema层面的多租户(一个租户一个数据库schema) 39 | - `SqlMigrate` - 数据库迁移工具, 每次更新平滑迁移 40 | - `Asynq` - 分布式定时任务(异步任务) 41 | - `Log` - 日志, 在Kratos基础上增加一层包装, 无需每个方法传入 42 | - `Embed` - go 1.16文件嵌入属性, 轻松将静态文件打包到编译后的二进制应用中 43 | - `Opentelemetry` - 链路追踪, 跨服务调用可快速追踪完整链路 44 | - `Idempotent` - 接口幂等性(解决重复点击或提交) 45 | - `Pprof` - 内置性能分析开关, 对并发/性能测试友好 46 | - `Wire` - 依赖注入, 编译时完成依赖注入 47 | - `Swagger` - Api文档一键生成, 无需在代码里写注解 48 | - `I18n` - 国际化支持, 简单切换多语言 49 | - `Minio` - 对象存储 50 | 51 | # 当前版本 Current version 52 | 53 | 建议直接使用最后一个版本 54 | 55 | ```bash 56 | git clone -b v1.1.1 https://github.com/go-cinch/auth 57 | ``` 58 | 59 | ## 在线演示 60 | 61 | [Vue3入口](https://vue3.go-cinch.top/) super/cinch123(超级管理员) guest/cinch123(访客) write/cinch123(读写权限) readonly/cinch123(只读权限) nodelete/cinch123(不能删除权限) 62 | [React入口](https://react.go-cinch.top/) 同上 63 | [Argocd入口](https://argocd.go-cinch.top/) guest/guest123 64 | [Asynqmon入口](https://asynqmon.go-cinch.top/) 查看定时任务运行情况 65 | [Minio入口](https://minio.go-cinch.top/) 对象存储 cinch/cinch123456 66 | [Grafana入口](https://grafana.go-cinch.top/) 监控面板 cinch/cinch123456 67 | 68 | # 项目模板 69 | 70 | [Auth](https://github.com/go-cinch/auth)是基于[layout](https://github.com/go-cinch/layout)生成的一个通用权限验证微服务, 71 | 节省鉴权服务搭建时间, 快速投入业务开发. 72 | 73 | # 行为管理 74 | 75 | ## 为什么不用casbin 76 | 77 | Golang主流的权限管理基本上用的是[Casbin](https://casbin.org), 曾经的项目中也用到过, 78 | 如[gin-web](https://github.com/piupuer/gin-web), 设计模式很好, 79 | 但实际使用过程中遇到一些问题 80 | 81 | 以RBAC为例以及结合常见的web管理系统, 你大概需要这么设计 82 | 83 | - api表 - 接口表, 存储所有的api, 如`GET /user`, `POST /login`(method+path) 84 | - casbin表 - casbin关系表, 存储对象和api之间的关系, 如用户admin有`GET /user`的访问权限, 85 | 数据记录可能是`v0=admin, v1=GET, v2=/user` 86 | - menu表 - 菜单表(可能包含关联表, 这里不细说), 存储所有的菜单 87 | - 和api关联, 如`用户管理`菜单下有`GET /user`, `POST /user`, `PATCH /user`, `DELETE /user` 88 | - 和role关联, 如角色是超级管理员可以展示所有页面, 访客只能查看首页 89 | - 若还需要增加单个按钮, 那需要btn表, 和menu类似 90 | - 若还需要单个用户权限不一样, 那user表与menu/btn要建立关联 91 | - 若还需要用户组权限, 那用户组与上面的表要建立关联 92 | 93 | 那么问题来了 94 | 95 | 1. 新增或删除接口, 你怎么维护上述各个表之间的关联关系? 96 | 2. app没有菜单, 怎么来设计 97 | 98 | > 或许这并不是casbin的问题, 这是业务层的问题. 你有更好的方案欢迎讨论 99 | 100 | ## 表结构 101 | 102 | ### Action 103 | 104 | - `name` - 名称, 可以重复, 定义行为名, 描述当前action用途 105 | - `code` - 唯一标识, 主要用于其他表关联 106 | - `word` - 英文关键字, 唯一, 方便前端展示 107 | - `resource` - 资源列表(\n隔开), 可以是http接口地址, 也可以是grpc接口地址 108 | - `menu` - 菜单列表(\n隔开), 和前端路由有关 109 | - `btn` - 按钮列表(\n隔开), 和前端有关 110 | 111 | > Tip: menu/btn是给前端看的, 前端来决定是否需要显示, 后端不单独存储menu表/btn表 112 | 113 | ### User/Role/UserGroup 114 | 115 | - `action` - 行为code列表(逗号隔开), 包含用户/角色/用户组具有的所有行为 116 | 117 | ## 优缺点 118 | 119 | 优点 120 | 121 | 1. 权限变更高效 122 | - 程序员只需关心action表, 修改action表对应内容即可 123 | - 使用者勾选或添加action, action通过name显示, 简单易懂 124 | 2. action不区分pc/app 125 | 3. 减少关联表 126 | 127 | 缺点 128 | 129 | 1. 增加冗余性(这个和减少关联表是相悖的) 130 | 2. 菜单等更新由前端管理, 若需更新, 必须重新发布前端代码 131 | 132 | ## 调用链路 133 | 134 | ### `http` 135 | 136 | http接口推荐配合nginx auth_request使用 137 | 138 | 1. 所有请求全部通过auth_request转发至`/permission` 139 | 2. 校验当前用户是否有resource的权限, 有直接返回(resource=`method`+`path`) 140 | 3. 校验当前用户所在角色是否有参数resource的权限, 有直接返回 141 | 4. 校验当前用户所在用户组是否有参数resource的权限 142 | 143 | ### `grpc` 144 | 145 | grpc无法使用nginx auth_request 146 | 147 | 1. 所有请求直接到各自微服务 148 | 2. 通过权限中间件转发到`/auth.v1.Auth/Permission`, auth服务执行后续逻辑 149 | 3. 校验当前用户是否有resource的权限, 有直接返回(resource=`/game.v1.Game/FindGame`) 150 | 4. 校验当前用户所在角色是否有参数resource的权限, 有直接返回 151 | 5. 校验当前用户所在用户组是否有参数resource的权限 152 | 153 | 权限源码参见[auth.Permission](https://github.com/go-cinch/auth/blob/v1.0.3/internal/service/auth.go#L122) 154 | 155 | 中间件参加[layout.Permission](https://github.com/go-cinch/layout/blob/v1.0.3/internal/server/middleware/permission.go#L20) 156 | 157 | # 常用接口 158 | 159 | - `/auth.v1.Auth/Register` - 用户注册 160 | - `/auth.v1.Auth/Pwd` - 修改密码 161 | - `/auth.v1.Auth/Status` - 获取用户状态, 是否锁定/需要输入验证码等 162 | - `/auth.v1.Auth/Captcha` - 获取验证码base64图片, 默认4位数字 163 | - `/auth.v1.Auth/Login` - 登入, 获取jwt token 164 | - `/auth.v1.Auth/Logout` - 登出 165 | - `/auth.v1.Auth/Refresh` - 刷新jwt token 166 | - `/auth.v1.Auth/Idempotent` - 获取幂等性token(登陆后) 167 | - `/auth.v1.Auth/Info` - 获取用户信息(登陆后) 168 | 169 | 其他接口是Action/User/Role/UserGroup的CRUD, 170 | 完整接口参见[auth.proto](https://github.com/go-cinch/auth-proto/blob/master/auth.proto) 171 | 172 | # 更多 173 | 174 | 微服务设计思想可参考[Kratos] 175 | 176 | [Kratos]: (https://go-kratos.dev/docs/) 177 | -------------------------------------------------------------------------------- /api/reason/reason.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v5.28.2 5 | // source: reason-proto/reason.proto 6 | 7 | package reason 8 | 9 | import ( 10 | _ "github.com/go-kratos/kratos/v2/errors" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type ErrorReason int32 25 | 26 | const ( 27 | ErrorReason_INTERNAL ErrorReason = 0 28 | ErrorReason_TOO_MANY_REQUESTS ErrorReason = 1 29 | ErrorReason_ILLEGAL_PARAMETER ErrorReason = 2 30 | ErrorReason_NOT_FOUND ErrorReason = 3 31 | ErrorReason_UNAUTHORIZED ErrorReason = 4 32 | ErrorReason_FORBIDDEN ErrorReason = 5 33 | ) 34 | 35 | // Enum value maps for ErrorReason. 36 | var ( 37 | ErrorReason_name = map[int32]string{ 38 | 0: "INTERNAL", 39 | 1: "TOO_MANY_REQUESTS", 40 | 2: "ILLEGAL_PARAMETER", 41 | 3: "NOT_FOUND", 42 | 4: "UNAUTHORIZED", 43 | 5: "FORBIDDEN", 44 | } 45 | ErrorReason_value = map[string]int32{ 46 | "INTERNAL": 0, 47 | "TOO_MANY_REQUESTS": 1, 48 | "ILLEGAL_PARAMETER": 2, 49 | "NOT_FOUND": 3, 50 | "UNAUTHORIZED": 4, 51 | "FORBIDDEN": 5, 52 | } 53 | ) 54 | 55 | func (x ErrorReason) Enum() *ErrorReason { 56 | p := new(ErrorReason) 57 | *p = x 58 | return p 59 | } 60 | 61 | func (x ErrorReason) String() string { 62 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 63 | } 64 | 65 | func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { 66 | return file_reason_proto_reason_proto_enumTypes[0].Descriptor() 67 | } 68 | 69 | func (ErrorReason) Type() protoreflect.EnumType { 70 | return &file_reason_proto_reason_proto_enumTypes[0] 71 | } 72 | 73 | func (x ErrorReason) Number() protoreflect.EnumNumber { 74 | return protoreflect.EnumNumber(x) 75 | } 76 | 77 | // Deprecated: Use ErrorReason.Descriptor instead. 78 | func (ErrorReason) EnumDescriptor() ([]byte, []int) { 79 | return file_reason_proto_reason_proto_rawDescGZIP(), []int{0} 80 | } 81 | 82 | var File_reason_proto_reason_proto protoreflect.FileDescriptor 83 | 84 | var file_reason_proto_reason_proto_rawDesc = []byte{ 85 | 0x0a, 0x19, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 86 | 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x65, 0x61, 87 | 0x73, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x13, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x65, 88 | 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2a, 0x9d, 0x01, 0x0a, 0x0b, 89 | 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x08, 0x49, 90 | 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x00, 0x1a, 0x04, 0xa8, 0x45, 0xf4, 0x03, 0x12, 91 | 0x1b, 0x0a, 0x11, 0x54, 0x4f, 0x4f, 0x5f, 0x4d, 0x41, 0x4e, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 92 | 0x45, 0x53, 0x54, 0x53, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0xad, 0x03, 0x12, 0x1b, 0x0a, 0x11, 93 | 0x49, 0x4c, 0x4c, 0x45, 0x47, 0x41, 0x4c, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 94 | 0x52, 0x10, 0x02, 0x1a, 0x04, 0xa8, 0x45, 0x90, 0x03, 0x12, 0x13, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 95 | 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x1a, 0x04, 0xa8, 0x45, 0x90, 0x03, 0x12, 0x16, 96 | 0x0a, 0x0c, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x04, 97 | 0x1a, 0x04, 0xa8, 0x45, 0x91, 0x03, 0x12, 0x13, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 98 | 0x44, 0x45, 0x4e, 0x10, 0x05, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x42, 0x30, 0x0a, 0x09, 0x72, 99 | 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x13, 0x2e, 0x2f, 0x61, 0x70, 100 | 0x69, 0x2f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x3b, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0xa2, 101 | 0x02, 0x0b, 0x41, 0x50, 0x49, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x56, 0x31, 0x62, 0x06, 0x70, 102 | 0x72, 0x6f, 0x74, 0x6f, 0x33, 103 | } 104 | 105 | var ( 106 | file_reason_proto_reason_proto_rawDescOnce sync.Once 107 | file_reason_proto_reason_proto_rawDescData = file_reason_proto_reason_proto_rawDesc 108 | ) 109 | 110 | func file_reason_proto_reason_proto_rawDescGZIP() []byte { 111 | file_reason_proto_reason_proto_rawDescOnce.Do(func() { 112 | file_reason_proto_reason_proto_rawDescData = protoimpl.X.CompressGZIP(file_reason_proto_reason_proto_rawDescData) 113 | }) 114 | return file_reason_proto_reason_proto_rawDescData 115 | } 116 | 117 | var file_reason_proto_reason_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 118 | var file_reason_proto_reason_proto_goTypes = []interface{}{ 119 | (ErrorReason)(0), // 0: reason.v1.ErrorReason 120 | } 121 | var file_reason_proto_reason_proto_depIdxs = []int32{ 122 | 0, // [0:0] is the sub-list for method output_type 123 | 0, // [0:0] is the sub-list for method input_type 124 | 0, // [0:0] is the sub-list for extension type_name 125 | 0, // [0:0] is the sub-list for extension extendee 126 | 0, // [0:0] is the sub-list for field type_name 127 | } 128 | 129 | func init() { file_reason_proto_reason_proto_init() } 130 | func file_reason_proto_reason_proto_init() { 131 | if File_reason_proto_reason_proto != nil { 132 | return 133 | } 134 | type x struct{} 135 | out := protoimpl.TypeBuilder{ 136 | File: protoimpl.DescBuilder{ 137 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 138 | RawDescriptor: file_reason_proto_reason_proto_rawDesc, 139 | NumEnums: 1, 140 | NumMessages: 0, 141 | NumExtensions: 0, 142 | NumServices: 0, 143 | }, 144 | GoTypes: file_reason_proto_reason_proto_goTypes, 145 | DependencyIndexes: file_reason_proto_reason_proto_depIdxs, 146 | EnumInfos: file_reason_proto_reason_proto_enumTypes, 147 | }.Build() 148 | File_reason_proto_reason_proto = out.File 149 | file_reason_proto_reason_proto_rawDesc = nil 150 | file_reason_proto_reason_proto_goTypes = nil 151 | file_reason_proto_reason_proto_depIdxs = nil 152 | } 153 | -------------------------------------------------------------------------------- /api/reason/reason.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: reason-proto/reason.proto 3 | 4 | package reason 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "sort" 15 | "strings" 16 | "time" 17 | "unicode/utf8" 18 | 19 | "google.golang.org/protobuf/types/known/anypb" 20 | ) 21 | 22 | // ensure the imports are used 23 | var ( 24 | _ = bytes.MinRead 25 | _ = errors.New("") 26 | _ = fmt.Print 27 | _ = utf8.UTFMax 28 | _ = (*regexp.Regexp)(nil) 29 | _ = (*strings.Reader)(nil) 30 | _ = net.IPv4len 31 | _ = time.Duration(0) 32 | _ = (*url.URL)(nil) 33 | _ = (*mail.Address)(nil) 34 | _ = anypb.Any{} 35 | _ = sort.Sort 36 | ) 37 | -------------------------------------------------------------------------------- /api/reason/reason_errors.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-errors. DO NOT EDIT. 2 | 3 | package reason 4 | 5 | import ( 6 | fmt "fmt" 7 | errors "github.com/go-kratos/kratos/v2/errors" 8 | ) 9 | 10 | // This is a compile-time assertion to ensure that this generated file 11 | // is compatible with the kratos package it is being compiled against. 12 | const _ = errors.SupportPackageIsVersion1 13 | 14 | func IsInternal(err error) bool { 15 | if err == nil { 16 | return false 17 | } 18 | e := errors.FromError(err) 19 | return e.Reason == ErrorReason_INTERNAL.String() && e.Code == 500 20 | } 21 | 22 | func ErrorInternal(format string, args ...interface{}) *errors.Error { 23 | return errors.New(500, ErrorReason_INTERNAL.String(), fmt.Sprintf(format, args...)) 24 | } 25 | 26 | func IsTooManyRequests(err error) bool { 27 | if err == nil { 28 | return false 29 | } 30 | e := errors.FromError(err) 31 | return e.Reason == ErrorReason_TOO_MANY_REQUESTS.String() && e.Code == 429 32 | } 33 | 34 | func ErrorTooManyRequests(format string, args ...interface{}) *errors.Error { 35 | return errors.New(429, ErrorReason_TOO_MANY_REQUESTS.String(), fmt.Sprintf(format, args...)) 36 | } 37 | 38 | func IsIllegalParameter(err error) bool { 39 | if err == nil { 40 | return false 41 | } 42 | e := errors.FromError(err) 43 | return e.Reason == ErrorReason_ILLEGAL_PARAMETER.String() && e.Code == 400 44 | } 45 | 46 | func ErrorIllegalParameter(format string, args ...interface{}) *errors.Error { 47 | return errors.New(400, ErrorReason_ILLEGAL_PARAMETER.String(), fmt.Sprintf(format, args...)) 48 | } 49 | 50 | func IsNotFound(err error) bool { 51 | if err == nil { 52 | return false 53 | } 54 | e := errors.FromError(err) 55 | return e.Reason == ErrorReason_NOT_FOUND.String() && e.Code == 400 56 | } 57 | 58 | func ErrorNotFound(format string, args ...interface{}) *errors.Error { 59 | return errors.New(400, ErrorReason_NOT_FOUND.String(), fmt.Sprintf(format, args...)) 60 | } 61 | 62 | func IsUnauthorized(err error) bool { 63 | if err == nil { 64 | return false 65 | } 66 | e := errors.FromError(err) 67 | return e.Reason == ErrorReason_UNAUTHORIZED.String() && e.Code == 401 68 | } 69 | 70 | func ErrorUnauthorized(format string, args ...interface{}) *errors.Error { 71 | return errors.New(401, ErrorReason_UNAUTHORIZED.String(), fmt.Sprintf(format, args...)) 72 | } 73 | 74 | func IsForbidden(err error) bool { 75 | if err == nil { 76 | return false 77 | } 78 | e := errors.FromError(err) 79 | return e.Reason == ErrorReason_FORBIDDEN.String() && e.Code == 403 80 | } 81 | 82 | func ErrorForbidden(format string, args ...interface{}) *errors.Error { 83 | return errors.New(403, ErrorReason_FORBIDDEN.String(), fmt.Sprintf(format, args...)) 84 | } 85 | -------------------------------------------------------------------------------- /cmd/auth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "strconv" 7 | 8 | "auth/internal/conf" 9 | "github.com/go-cinch/common/log" 10 | "github.com/go-cinch/common/log/caller" 11 | _ "github.com/go-cinch/common/plugins/gorm/filter" 12 | "github.com/go-cinch/common/plugins/k8s/pod" 13 | "github.com/go-cinch/common/plugins/kratos/config/env" 14 | _ "github.com/go-cinch/common/plugins/kratos/encoding/yml" 15 | "github.com/go-cinch/common/utils" 16 | "github.com/go-kratos/kratos/v2" 17 | "github.com/go-kratos/kratos/v2/config" 18 | "github.com/go-kratos/kratos/v2/config/file" 19 | "github.com/go-kratos/kratos/v2/middleware/tracing" 20 | "github.com/go-kratos/kratos/v2/transport/grpc" 21 | "github.com/go-kratos/kratos/v2/transport/http" 22 | ) 23 | 24 | // go build -ldflags "-X main.Version=x.y.z" 25 | var ( 26 | // Name is the name of the compiled software. 27 | Name = "auth" 28 | // EnvPrefix is the prefix of the env params 29 | EnvPrefix = "SERVICE" 30 | // Version is the version of the compiled software. 31 | Version string 32 | // flagConf is the config flag. 33 | flagConf string 34 | // beforeReadConfigLogLevel is log level before read config. 35 | beforeReadConfigLogLevel = log.InfoLevel 36 | 37 | id, _ = os.Hostname() 38 | ) 39 | 40 | func init() { 41 | flag.StringVar(&flagConf, "c", "../../configs", "config path, eg: -c config.yml") 42 | } 43 | 44 | func newApp(gs *grpc.Server, hs *http.Server) *kratos.App { 45 | return kratos.New( 46 | kratos.ID(id), 47 | kratos.Name(Name), 48 | kratos.Version(Version), 49 | kratos.Metadata(map[string]string{}), 50 | kratos.Logger(log.DefaultWrapper.Options().Logger()), 51 | kratos.Server( 52 | gs, 53 | hs, 54 | ), 55 | ) 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | // set default log before read config 61 | logOps := []func(*log.Options){ 62 | log.WithJSON(true), 63 | log.WithLevel(beforeReadConfigLogLevel), 64 | log.WithValuer("service.id", id), 65 | log.WithValuer("service.name", Name), 66 | log.WithValuer("service.version", Version), 67 | log.WithValuer("trace.id", tracing.TraceID()), 68 | log.WithValuer("span.id", tracing.SpanID()), 69 | log.WithCallerOptions( 70 | caller.WithSource(false), 71 | caller.WithLevel(2), 72 | caller.WithVersion(true), 73 | ), 74 | } 75 | log.DefaultWrapper = log.NewWrapper(logOps...) 76 | c := config.New( 77 | config.WithSource(file.NewSource(flagConf)), 78 | config.WithResolver( 79 | env.NewRevolver( 80 | env.WithPrefix(EnvPrefix), 81 | env.WithLoaded(func(k string, v interface{}) { 82 | log.Info("env loaded: %s=%v", k, v) 83 | }), 84 | ), 85 | ), 86 | ) 87 | defer c.Close() 88 | 89 | fields := log.Fields{ 90 | "conf": flagConf, 91 | } 92 | if err := c.Load(); err != nil { 93 | log. 94 | WithError(err). 95 | WithFields(fields). 96 | Fatal("load conf failed") 97 | } 98 | 99 | var bc conf.Bootstrap 100 | if err := c.Scan(&bc); err != nil { 101 | log. 102 | WithError(err). 103 | WithFields(fields). 104 | Fatal("scan conf failed") 105 | } 106 | bc.Name = Name 107 | bc.Version = Version 108 | // override log level after read config 109 | logOps = append(logOps, 110 | []func(*log.Options){ 111 | log.WithLevel(log.NewLevel(bc.Log.Level)), 112 | log.WithJSON(bc.Log.JSON), 113 | }..., 114 | ) 115 | log.DefaultWrapper = log.NewWrapper(logOps...) 116 | if bc.Server.MachineId == "" { 117 | // if machine id not set, gen from pod ip 118 | machineId, err := pod.MachineID() 119 | if err == nil { 120 | bc.Server.MachineId = strconv.FormatUint(uint64(machineId), 10) 121 | } else { 122 | bc.Server.MachineId = "0" 123 | } 124 | } 125 | // os.Setenv("COPIERX_UTC", "true") 126 | 127 | app, cleanup, err := wireApp(&bc) 128 | if err != nil { 129 | str := utils.Struct2JSON(&bc) 130 | log. 131 | WithError(err). 132 | Error("wire app failed") 133 | // env str maybe very long, log with another line 134 | log. 135 | WithFields(fields). 136 | Fatal(str) 137 | } 138 | defer cleanup() 139 | 140 | // start and wait for stop signal 141 | if err = app.Run(); err != nil { 142 | log. 143 | WithError(err). 144 | WithFields(fields). 145 | Fatal("run app failed") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /cmd/auth/wire.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | // +build wireinject 3 | 4 | // The build tag makes sure the stub is not built in the final build. 5 | 6 | package main 7 | 8 | import ( 9 | "auth/internal/biz" 10 | "auth/internal/conf" 11 | "auth/internal/data" 12 | "auth/internal/pkg/task" 13 | "auth/internal/server" 14 | "auth/internal/service" 15 | "github.com/go-kratos/kratos/v2" 16 | "github.com/google/wire" 17 | ) 18 | 19 | // wireApp init kratos application. 20 | func wireApp(c *conf.Bootstrap) (*kratos.App, func(), error) { 21 | panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, task.ProviderSet, service.ProviderSet, newApp)) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/auth/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run -mod=mod github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package main 8 | 9 | import ( 10 | "auth/internal/biz" 11 | "auth/internal/conf" 12 | "auth/internal/data" 13 | "auth/internal/pkg/task" 14 | "auth/internal/server" 15 | "auth/internal/service" 16 | "github.com/go-kratos/kratos/v2" 17 | ) 18 | 19 | import ( 20 | _ "github.com/go-cinch/common/plugins/gorm/filter" 21 | _ "github.com/go-cinch/common/plugins/kratos/encoding/yml" 22 | ) 23 | 24 | // Injectors from wire.go: 25 | 26 | // wireApp init kratos application. 27 | func wireApp(c *conf.Bootstrap) (*kratos.App, func(), error) { 28 | universalClient, err := data.NewRedis(c) 29 | if err != nil { 30 | return nil, nil, err 31 | } 32 | tenant, err := data.NewDB(c) 33 | if err != nil { 34 | return nil, nil, err 35 | } 36 | sonyflake, err := data.NewSonyflake(c) 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | tracerProvider, err := data.NewTracer(c) 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | dataData, cleanup := data.NewData(universalClient, tenant, sonyflake, tracerProvider) 45 | hotspotRepo := data.NewHotspotRepo(c, dataData) 46 | actionRepo := data.NewActionRepo(c, dataData, hotspotRepo) 47 | userRepo := data.NewUserRepo(dataData, actionRepo) 48 | transaction := data.NewTransaction(dataData) 49 | cache := data.NewCache(c, universalClient) 50 | userUseCase := biz.NewUserUseCase(c, userRepo, hotspotRepo, transaction, cache) 51 | hotspotUseCase := biz.NewHotspotUseCase(c, hotspotRepo) 52 | worker, err := task.New(c, userUseCase, hotspotUseCase) 53 | if err != nil { 54 | cleanup() 55 | return nil, nil, err 56 | } 57 | actionUseCase := biz.NewActionUseCase(c, actionRepo, transaction, cache) 58 | roleRepo := data.NewRoleRepo(dataData, actionRepo) 59 | roleUseCase := biz.NewRoleUseCase(c, roleRepo, transaction, cache) 60 | userGroupRepo := data.NewUserGroupRepo(dataData, actionRepo, userRepo) 61 | userGroupUseCase := biz.NewUserGroupUseCase(c, userGroupRepo, transaction, cache) 62 | permissionRepo := data.NewPermissionRepo(dataData, actionRepo, hotspotRepo) 63 | permissionUseCase := biz.NewPermissionUseCase(c, permissionRepo) 64 | whitelistRepo := data.NewWhitelistRepo(dataData, actionRepo, hotspotRepo) 65 | whitelistUseCase := biz.NewWhitelistUseCase(c, whitelistRepo, transaction, cache) 66 | authService := service.NewAuthService(c, worker, userUseCase, actionUseCase, roleUseCase, userGroupUseCase, permissionUseCase, whitelistUseCase) 67 | grpcServer := server.NewGRPCServer(c, authService, universalClient, whitelistUseCase) 68 | httpServer := server.NewHTTPServer(c, authService, universalClient, whitelistUseCase) 69 | app := newApp(grpcServer, httpServer) 70 | return app, func() { 71 | cleanup() 72 | }, nil 73 | } 74 | -------------------------------------------------------------------------------- /configs/config.yml: -------------------------------------------------------------------------------- 1 | server: 2 | prod: false 3 | machineId: '' 4 | http: 5 | addr: 0.0.0.0:6060 6 | timeout: 20s 7 | grpc: 8 | addr: 0.0.0.0:6160 9 | timeout: 20s 10 | language: 'en' 11 | jwt: 12 | # enable jwt, default use jwtV4 and HS512 algorithm, see: https://github.com/go-cinch/common/blob/jwt/v1.0.3/jwt/jwt.go#L123 13 | enable: true 14 | key: 'auth' 15 | expires: '24h' 16 | permission: true 17 | idempotent: true 18 | validate: true 19 | # disable biz.Cache or not 20 | nocache: false 21 | data: 22 | database: 23 | driver: mysql 24 | # dsn: 'root:root@tcp(127.0.0.1:3306)/auth?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 25 | dsn: '' 26 | endpoint: '127.0.0.1:3306' 27 | username: 'root' 28 | password: 'passwd' 29 | schema: 'auth' 30 | query: 'charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 31 | # tenants: 32 | # auth.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/auth?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms}' 33 | # auth2.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/auth2?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 34 | # auth3.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/auth3?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 35 | # auth4.go-cinch.top: 'root:root@tcp(127.0.0.1:3306)/auth4?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 36 | redis: 37 | # redis dsn like this: 38 | # redis://[:password@]host[:port][/dbnumber] 39 | # redis://[:password@]host1[:port][,host2:[:port]][,hostN:[:port]][?master=masterName&sentinel=true] 40 | dsn: 'redis://127.0.0.1:6379/0' 41 | -------------------------------------------------------------------------------- /configs/gen.yml: -------------------------------------------------------------------------------- 1 | # go install github.com/go-cinch/cinch/cmd/cinch@latest 2 | # cinch gen gorm 3 | gen: 4 | dsn: 'root:passwd@tcp(127.0.0.1:3306)/auth?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&timeout=10000ms' 5 | tables: 6 | - user 7 | - action 8 | - role 9 | - user_group 10 | - user_user_group_relation 11 | - whitelist 12 | exclude: 13 | - schema_migrations 14 | association: 15 | - 'user_group|user|Users|many_to_many|many2many:user_user_group_relation' 16 | - 'user|role|Role|has_one|foreignKey:RoleID' 17 | field-with-string-tag: 18 | - 'user|role_id' 19 | - 'user_group|lock_expire|wrong' 20 | - 'user_user_group_relation|user_id|user_group_id' -------------------------------------------------------------------------------- /configs/hotspot.yml: -------------------------------------------------------------------------------- 1 | hotspot: 2 | # cache name prefix 3 | name: 'hotspot' 4 | # cache expire time for each key 5 | expire: 86400s -------------------------------------------------------------------------------- /configs/log.yml: -------------------------------------------------------------------------------- 1 | log: 2 | level: 'info' 3 | # text or json 4 | JSON: true 5 | # show sql or not 6 | showSQL: true -------------------------------------------------------------------------------- /configs/task.yml: -------------------------------------------------------------------------------- 1 | task: 2 | # cron task list 3 | cron: 4 | refresh.hotspot: 5 | name: 'refresh.hotspot' 6 | expr: '0 0/5 * * * * *' 7 | # all group name list(cron or once) 8 | group: 9 | # login failed will delay to update wrong count(once) 10 | loginFailed: 'login.failed' 11 | # login success will delay to update last login time(once) 12 | loginLast: 'login.last' 13 | # refresh hotspot(cron) 14 | refreshHotspot: 'refresh.hotspot' 15 | # refresh hotspot manual(once) 16 | refreshHotspotManual: 'refresh.hotspot.manual' 17 | -------------------------------------------------------------------------------- /configs/tracer.yml: -------------------------------------------------------------------------------- 1 | tracer: 2 | # enable tracer or not, if gcp=false, otlp endpoint='', will use stdout 3 | enable: true 4 | # samples ratio 5 | ratio: 1 6 | otlp: 7 | # how to run a otel-collector? see this: https://github.com/SigNoz/signoz/blob/develop/deploy/README.md#using-docker-compose 8 | # endpoint: '127.0.0.1:4317' 9 | endpoint: '' 10 | insecure: true 11 | stdout: 12 | prettyPrint: false 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module auth 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/bsm/redislock v0.9.4 7 | github.com/envoyproxy/protoc-gen-validate v1.0.4 8 | github.com/go-cinch/common/captcha v1.0.5 9 | github.com/go-cinch/common/constant v1.0.5 10 | github.com/go-cinch/common/copierx v1.0.4 11 | github.com/go-cinch/common/i18n v1.0.7 12 | github.com/go-cinch/common/id v1.0.6 13 | github.com/go-cinch/common/idempotent v1.1.0 14 | github.com/go-cinch/common/jwt v1.0.4 15 | github.com/go-cinch/common/log v1.2.0 16 | github.com/go-cinch/common/middleware/i18n v1.0.6 17 | github.com/go-cinch/common/middleware/logging v1.0.1 18 | github.com/go-cinch/common/middleware/tenant v1.0.2 19 | github.com/go-cinch/common/middleware/trace v1.0.3 20 | github.com/go-cinch/common/page v1.0.5 21 | github.com/go-cinch/common/plugins/gorm/filter v1.0.3 22 | github.com/go-cinch/common/plugins/gorm/log v1.0.5 23 | github.com/go-cinch/common/plugins/gorm/tenant v1.0.3 24 | github.com/go-cinch/common/plugins/k8s/pod v1.0.1 25 | github.com/go-cinch/common/plugins/kratos/config/env v1.0.3 26 | github.com/go-cinch/common/plugins/kratos/encoding/yml v1.0.2 27 | github.com/go-cinch/common/proto/params v1.0.1 28 | github.com/go-cinch/common/utils v1.0.5 29 | github.com/go-cinch/common/worker v1.1.0 30 | github.com/go-kratos/kratos/v2 v2.8.3 31 | github.com/gobwas/glob v0.2.3 32 | github.com/golang-jwt/jwt/v4 v4.5.1 33 | github.com/golang-module/carbon/v2 v2.3.12 34 | github.com/google/wire v0.6.0 35 | github.com/patrickmn/go-cache v2.1.0+incompatible 36 | github.com/pkg/errors v0.9.1 37 | github.com/redis/go-redis/v9 v9.7.0 38 | github.com/samber/lo v1.49.1 39 | go.opentelemetry.io/otel v1.34.0 40 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 41 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 42 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 43 | go.opentelemetry.io/otel/sdk v1.30.0 44 | golang.org/x/crypto v0.27.0 45 | golang.org/x/text v0.21.0 46 | google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 47 | google.golang.org/grpc v1.66.1 48 | google.golang.org/protobuf v1.36.5 49 | gorm.io/gen v0.3.26 50 | gorm.io/gorm v1.25.9 51 | gorm.io/plugin/dbresolver v1.5.0 52 | ) 53 | 54 | require ( 55 | dario.cat/mergo v1.0.0 // indirect 56 | github.com/BurntSushi/toml v1.3.2 // indirect 57 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 58 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 59 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 60 | github.com/fsnotify/fsnotify v1.6.0 // indirect 61 | github.com/go-cinch/common/migrate v1.0.5 // indirect 62 | github.com/go-cinch/common/queue/stream v1.0.0 // indirect 63 | github.com/go-gorp/gorp/v3 v3.1.0 // indirect 64 | github.com/go-kratos/aegis v0.2.0 // indirect 65 | github.com/go-logr/logr v1.4.2 // indirect 66 | github.com/go-logr/stdr v1.2.2 // indirect 67 | github.com/go-ole/go-ole v1.2.6 // indirect 68 | github.com/go-playground/form/v4 v4.2.1 // indirect 69 | github.com/go-sql-driver/mysql v1.7.1 // indirect 70 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 71 | github.com/google/uuid v1.6.0 // indirect 72 | github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 // indirect 73 | github.com/gorilla/mux v1.8.1 // indirect 74 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect 75 | github.com/hibiken/asynq v0.25.1 // indirect 76 | github.com/jinzhu/copier v0.4.0 // indirect 77 | github.com/jinzhu/inflection v1.0.0 // indirect 78 | github.com/jinzhu/now v1.1.5 // indirect 79 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect 80 | github.com/mojocn/base64Captcha v1.3.5 // indirect 81 | github.com/nicksnyder/go-i18n/v2 v2.2.1 // indirect 82 | github.com/paulbellamy/ratecounter v0.2.0 // indirect 83 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect 84 | github.com/r3labs/diff/v3 v3.0.1 // indirect 85 | github.com/robfig/cron/v3 v3.0.1 // indirect 86 | github.com/rubenv/sql-migrate v1.5.1 // indirect 87 | github.com/shirou/gopsutil/v3 v3.23.6 // indirect 88 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 89 | github.com/sirupsen/logrus v1.9.3 // indirect 90 | github.com/sony/sonyflake v1.1.0 // indirect 91 | github.com/spf13/cast v1.7.0 // indirect 92 | github.com/tklauser/go-sysconf v0.3.11 // indirect 93 | github.com/tklauser/numcpus v0.6.1 // indirect 94 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 95 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 96 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 97 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 98 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 99 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 100 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 101 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect 102 | golang.org/x/mod v0.17.0 // indirect 103 | golang.org/x/net v0.29.0 // indirect 104 | golang.org/x/sync v0.10.0 // indirect 105 | golang.org/x/sys v0.27.0 // indirect 106 | golang.org/x/time v0.8.0 // indirect 107 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 108 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 109 | gopkg.in/yaml.v3 v3.0.1 // indirect 110 | gorm.io/datatypes v1.2.0 // indirect 111 | gorm.io/driver/mysql v1.5.1 // indirect 112 | gorm.io/hints v1.1.2 // indirect 113 | ) 114 | -------------------------------------------------------------------------------- /internal/biz/README.md: -------------------------------------------------------------------------------- 1 | # Biz 2 | -------------------------------------------------------------------------------- /internal/biz/action.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/conf" 8 | "github.com/go-cinch/common/page" 9 | "github.com/go-cinch/common/utils" 10 | ) 11 | 12 | type Action struct { 13 | Id uint64 `json:"id,string"` 14 | Code string `json:"code"` 15 | Name string `json:"name"` 16 | Word string `json:"word"` 17 | Resource string `json:"resource"` 18 | Menu string `json:"menu"` 19 | Btn string `json:"btn"` 20 | } 21 | 22 | type FindAction struct { 23 | Page page.Page `json:"page"` 24 | Code *string `json:"code"` 25 | Name *string `json:"name"` 26 | Word *string `json:"word"` 27 | Resource *string `json:"resource"` 28 | } 29 | 30 | type FindActionCache struct { 31 | Page page.Page `json:"page"` 32 | List []Action `json:"list"` 33 | } 34 | 35 | type UpdateAction struct { 36 | Id uint64 `json:"id,string"` 37 | Name *string `json:"name,omitempty"` 38 | Word *string `json:"word,omitempty"` 39 | Resource *string `json:"resource,omitempty"` 40 | Menu *string `json:"menu,omitempty"` 41 | Btn *string `json:"btn,omitempty"` 42 | } 43 | 44 | type ActionRepo interface { 45 | Create(ctx context.Context, item *Action) error 46 | GetDefault(ctx context.Context) Action 47 | Find(ctx context.Context, condition *FindAction) []Action 48 | FindByCode(ctx context.Context, code string) []Action 49 | Update(ctx context.Context, item *UpdateAction) error 50 | Delete(ctx context.Context, ids ...uint64) error 51 | CodeExists(ctx context.Context, code string) error 52 | Permission(ctx context.Context, code string, req *CheckPermission) bool 53 | MatchResource(ctx context.Context, resource string, req *CheckPermission) bool 54 | } 55 | 56 | type ActionUseCase struct { 57 | c *conf.Bootstrap 58 | repo ActionRepo 59 | tx Transaction 60 | cache Cache 61 | } 62 | 63 | func NewActionUseCase(c *conf.Bootstrap, repo ActionRepo, tx Transaction, cache Cache) *ActionUseCase { 64 | return &ActionUseCase{ 65 | c: c, 66 | repo: repo, 67 | tx: tx, 68 | cache: cache.WithPrefix("action"), 69 | } 70 | } 71 | 72 | func (uc *ActionUseCase) Create(ctx context.Context, item *Action) error { 73 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 74 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 75 | return uc.repo.Create(ctx, item) 76 | }) 77 | }) 78 | } 79 | 80 | func (uc *ActionUseCase) Find(ctx context.Context, condition *FindAction) (rp []Action, err error) { 81 | action := strings.Join([]string{"find", utils.StructMd5(condition)}, "_") 82 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 83 | return uc.find(ctx, action, condition) 84 | }) 85 | if err != nil { 86 | return 87 | } 88 | var cache FindActionCache 89 | utils.JSON2Struct(&cache, str) 90 | condition.Page = cache.Page 91 | rp = cache.List 92 | return 93 | } 94 | 95 | func (uc *ActionUseCase) find(ctx context.Context, action string, condition *FindAction) (res string, err error) { 96 | // read data from db and write to cache 97 | list := uc.repo.Find(ctx, condition) 98 | var cache FindActionCache 99 | cache.List = list 100 | cache.Page = condition.Page 101 | res = utils.Struct2JSON(cache) 102 | uc.cache.Set(ctx, action, res, len(list) == 0) 103 | return 104 | } 105 | 106 | func (uc *ActionUseCase) Update(ctx context.Context, item *UpdateAction) error { 107 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 108 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 109 | err = uc.repo.Update(ctx, item) 110 | return 111 | }) 112 | }) 113 | } 114 | 115 | func (uc *ActionUseCase) Delete(ctx context.Context, ids ...uint64) error { 116 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 117 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 118 | err = uc.repo.Delete(ctx, ids...) 119 | return 120 | }) 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /internal/biz/biz.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/wire" 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | // ProviderSet is biz providers. 11 | var ProviderSet = wire.NewSet( 12 | NewUserUseCase, 13 | NewActionUseCase, 14 | NewRoleUseCase, 15 | NewUserGroupUseCase, 16 | NewPermissionUseCase, 17 | NewWhitelistUseCase, 18 | NewHotspotUseCase, 19 | ) 20 | 21 | type Transaction interface { 22 | Tx(ctx context.Context, handler func(context.Context) error) error 23 | } 24 | 25 | type Cache interface { 26 | // Cache is get redis instance 27 | Cache() redis.UniversalClient 28 | // WithPrefix will add cache key prefix 29 | WithPrefix(prefix string) Cache 30 | // WithRefresh get data from db skip cache and refresh cache 31 | WithRefresh() Cache 32 | // Get is get cache data by key from redis, do write handler if cache is empty 33 | Get(ctx context.Context, action string, write func(context.Context) (string, error)) (string, error) 34 | // Set is set data to redis 35 | Set(ctx context.Context, action, data string, short bool) 36 | // Del delete key 37 | Del(ctx context.Context, action string) 38 | // SetWithExpiration is set data to redis with custom expiration 39 | SetWithExpiration(ctx context.Context, action, data string, seconds int64) 40 | // Flush is clean association cache if handler err=nil 41 | Flush(ctx context.Context, handler func(context.Context) error) error 42 | // FlushByPrefix clean cache by prefix, without prefix equals flush all by default cache prefix 43 | FlushByPrefix(ctx context.Context, prefix ...string) (err error) 44 | } 45 | -------------------------------------------------------------------------------- /internal/biz/hotspot.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/internal/conf" 7 | ) 8 | 9 | type HotspotRepo interface { 10 | Refresh(ctx context.Context) error 11 | GetUserByCode(ctx context.Context, code string) *User 12 | GetUserByUsername(ctx context.Context, username string) *User 13 | GetRoleByID(ctx context.Context, id uint64) *Role 14 | GetActionByWord(ctx context.Context, word string) *Action 15 | GetActionByCode(ctx context.Context, code string) *Action 16 | FindActionByCode(ctx context.Context, codes ...string) []Action 17 | FindWhitelistResourceByCategory(ctx context.Context, category uint32) []string 18 | FindUserGroupByUserCode(ctx context.Context, code string) []UserGroup 19 | } 20 | 21 | type HotspotUseCase struct { 22 | c *conf.Bootstrap 23 | repo HotspotRepo 24 | } 25 | 26 | func NewHotspotUseCase(c *conf.Bootstrap, repo HotspotRepo) *HotspotUseCase { 27 | return &HotspotUseCase{ 28 | c: c, 29 | repo: repo, 30 | } 31 | } 32 | 33 | func (uc *HotspotUseCase) Refresh(ctx context.Context) error { 34 | return uc.repo.Refresh(ctx) 35 | } 36 | -------------------------------------------------------------------------------- /internal/biz/permission.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/internal/conf" 7 | ) 8 | 9 | type Permission struct { 10 | Resources []string `json:"resources"` 11 | Menus []string `json:"menus"` 12 | Btns []string `json:"btns"` 13 | } 14 | 15 | type CheckPermission struct { 16 | UserCode string `json:"userCode"` 17 | Resource string `json:"resource"` 18 | Method string `json:"method"` 19 | URI string `json:"uri"` 20 | } 21 | 22 | type PermissionRepo interface { 23 | Check(ctx context.Context, item *CheckPermission) bool 24 | GetByUserCode(ctx context.Context, code string) *Permission 25 | } 26 | 27 | type PermissionUseCase struct { 28 | c *conf.Bootstrap 29 | repo PermissionRepo 30 | } 31 | 32 | func NewPermissionUseCase(c *conf.Bootstrap, repo PermissionRepo) *PermissionUseCase { 33 | return &PermissionUseCase{ 34 | c: c, 35 | repo: repo, 36 | } 37 | } 38 | 39 | func (uc *PermissionUseCase) Check(ctx context.Context, item *CheckPermission) (rp bool) { 40 | rp = uc.repo.Check(ctx, item) 41 | return 42 | } 43 | 44 | func (uc *PermissionUseCase) GetByUserCode(ctx context.Context, code string) (rp *Permission) { 45 | rp = uc.repo.GetByUserCode(ctx, code) 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /internal/biz/reason.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/reason" 7 | "github.com/go-cinch/common/constant" 8 | "github.com/go-cinch/common/middleware/i18n" 9 | ) 10 | 11 | var ( 12 | ErrJwtMissingToken = func(ctx context.Context) error { 13 | return i18n.NewError(ctx, constant.JwtMissingToken, reason.ErrorUnauthorized) 14 | } 15 | ErrJwtTokenInvalid = func(ctx context.Context) error { 16 | return i18n.NewError(ctx, constant.JwtTokenInvalid, reason.ErrorUnauthorized) 17 | } 18 | ErrJwtTokenExpired = func(ctx context.Context) error { 19 | return i18n.NewError(ctx, constant.JwtTokenExpired, reason.ErrorUnauthorized) 20 | } 21 | ErrJwtTokenParseFail = func(ctx context.Context) error { 22 | return i18n.NewError(ctx, constant.JwtTokenParseFail, reason.ErrorUnauthorized) 23 | } 24 | ErrJwtUnSupportSigningMethod = func(ctx context.Context) error { 25 | return i18n.NewError(ctx, constant.JwtUnSupportSigningMethod, reason.ErrorUnauthorized) 26 | } 27 | ErrIdempotentTokenExpired = func(ctx context.Context) error { 28 | return i18n.NewError(ctx, constant.IdempotentTokenExpired, reason.ErrorIllegalParameter) 29 | } 30 | 31 | ErrTooManyRequests = func(ctx context.Context) error { 32 | return i18n.NewError(ctx, constant.TooManyRequests, reason.ErrorTooManyRequests) 33 | } 34 | ErrDataNotChange = func(ctx context.Context, args ...string) error { 35 | return i18n.NewError(ctx, constant.DataNotChange, reason.ErrorIllegalParameter, args...) 36 | } 37 | ErrDuplicateField = func(ctx context.Context, args ...string) error { 38 | return i18n.NewError(ctx, constant.DuplicateField, reason.ErrorIllegalParameter, args...) 39 | } 40 | ErrRecordNotFound = func(ctx context.Context, args ...string) error { 41 | return i18n.NewError(ctx, constant.RecordNotFound, reason.ErrorNotFound, args...) 42 | } 43 | ErrNoPermission = func(ctx context.Context) error { 44 | return i18n.NewError(ctx, constant.NoPermission, reason.ErrorForbidden) 45 | } 46 | 47 | ErrIncorrectPassword = func(ctx context.Context) error { 48 | return i18n.NewError(ctx, constant.IncorrectPassword, reason.ErrorIllegalParameter) 49 | } 50 | ErrSamePassword = func(ctx context.Context) error { 51 | return i18n.NewError(ctx, constant.SamePassword, reason.ErrorIllegalParameter) 52 | } 53 | ErrInvalidCaptcha = func(ctx context.Context) error { 54 | return i18n.NewError(ctx, constant.InvalidCaptcha, reason.ErrorIllegalParameter) 55 | } 56 | ErrLoginFailed = func(ctx context.Context) error { 57 | return i18n.NewError(ctx, constant.LoginFailed, reason.ErrorIllegalParameter) 58 | } 59 | ErrUserLocked = func(ctx context.Context) error { 60 | return i18n.NewError(ctx, constant.UserLocked, reason.ErrorForbidden) 61 | } 62 | ErrKeepLeastOneAction = func(ctx context.Context) error { 63 | return i18n.NewError(ctx, constant.KeepLeastOneAction, reason.ErrorIllegalParameter) 64 | } 65 | ErrDeleteYourself = func(ctx context.Context) error { 66 | return i18n.NewError(ctx, constant.DeleteYourself, reason.ErrorIllegalParameter) 67 | } 68 | ) 69 | -------------------------------------------------------------------------------- /internal/biz/role.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/conf" 8 | "github.com/go-cinch/common/page" 9 | "github.com/go-cinch/common/utils" 10 | ) 11 | 12 | type Role struct { 13 | Id uint64 `json:"id,string"` 14 | Name string `json:"name"` 15 | Word string `json:"word"` 16 | Action string `json:"action"` 17 | Actions []Action `json:"actions"` 18 | } 19 | 20 | type FindRole struct { 21 | Page page.Page `json:"page"` 22 | Name *string `json:"name"` 23 | Word *string `json:"word"` 24 | Action *string `json:"action"` 25 | } 26 | 27 | type FindRoleCache struct { 28 | Page page.Page `json:"page"` 29 | List []Role `json:"list"` 30 | } 31 | 32 | type UpdateRole struct { 33 | Id uint64 `json:"id,string"` 34 | Name *string `json:"name,omitempty"` 35 | Word *string `json:"word,omitempty"` 36 | Action *string `json:"action,omitempty"` 37 | } 38 | 39 | type RoleRepo interface { 40 | Create(ctx context.Context, item *Role) error 41 | Find(ctx context.Context, condition *FindRole) []Role 42 | Update(ctx context.Context, item *UpdateRole) error 43 | Delete(ctx context.Context, ids ...uint64) error 44 | } 45 | 46 | type RoleUseCase struct { 47 | c *conf.Bootstrap 48 | repo RoleRepo 49 | tx Transaction 50 | cache Cache 51 | } 52 | 53 | func NewRoleUseCase(c *conf.Bootstrap, repo RoleRepo, tx Transaction, cache Cache) *RoleUseCase { 54 | return &RoleUseCase{ 55 | c: c, 56 | repo: repo, 57 | tx: tx, 58 | cache: cache.WithPrefix("role"), 59 | } 60 | } 61 | 62 | func (uc *RoleUseCase) Create(ctx context.Context, item *Role) error { 63 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 64 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 65 | return uc.repo.Create(ctx, item) 66 | }) 67 | }) 68 | } 69 | 70 | func (uc *RoleUseCase) Find(ctx context.Context, condition *FindRole) (rp []Role, err error) { 71 | action := strings.Join([]string{"find", utils.StructMd5(condition)}, "_") 72 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 73 | return uc.find(ctx, action, condition) 74 | }) 75 | if err != nil { 76 | return 77 | } 78 | var cache FindRoleCache 79 | utils.JSON2Struct(&cache, str) 80 | condition.Page = cache.Page 81 | rp = cache.List 82 | return 83 | } 84 | 85 | func (uc *RoleUseCase) find(ctx context.Context, action string, condition *FindRole) (res string, err error) { 86 | // read data from db and write to cache 87 | list := uc.repo.Find(ctx, condition) 88 | var cache FindRoleCache 89 | cache.List = list 90 | cache.Page = condition.Page 91 | res = utils.Struct2JSON(cache) 92 | uc.cache.Set(ctx, action, res, len(list) == 0) 93 | return 94 | } 95 | 96 | func (uc *RoleUseCase) Update(ctx context.Context, item *UpdateRole) error { 97 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 98 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 99 | return uc.repo.Update(ctx, item) 100 | }) 101 | }) 102 | } 103 | 104 | func (uc *RoleUseCase) Delete(ctx context.Context, ids ...uint64) error { 105 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 106 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 107 | err = uc.repo.Delete(ctx, ids...) 108 | return 109 | }) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /internal/biz/user_group.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/conf" 8 | "github.com/go-cinch/common/page" 9 | "github.com/go-cinch/common/utils" 10 | ) 11 | 12 | type UserGroup struct { 13 | Id uint64 `json:"id,string"` 14 | Users []User `json:"users"` 15 | Name string `json:"name"` 16 | Word string `json:"word"` 17 | Action string `json:"action"` 18 | Actions []Action `json:"actions"` 19 | } 20 | 21 | type FindUserGroup struct { 22 | Page page.Page `json:"page"` 23 | Name *string `json:"name"` 24 | Word *string `json:"word"` 25 | Action *string `json:"action"` 26 | } 27 | 28 | type FindUserGroupCache struct { 29 | Page page.Page `json:"page"` 30 | List []UserGroup `json:"list"` 31 | } 32 | 33 | type UpdateUserGroup struct { 34 | Id uint64 `json:"id,string"` 35 | Name *string `json:"name,omitempty"` 36 | Word *string `json:"word,omitempty"` 37 | Action *string `json:"action,omitempty"` 38 | Users *string `json:"users,omitempty"` 39 | } 40 | 41 | type UserGroupRepo interface { 42 | Create(ctx context.Context, item *UserGroup) error 43 | Find(ctx context.Context, condition *FindUserGroup) []UserGroup 44 | Update(ctx context.Context, item *UpdateUserGroup) error 45 | Delete(ctx context.Context, ids ...uint64) error 46 | } 47 | 48 | type UserGroupUseCase struct { 49 | c *conf.Bootstrap 50 | repo UserGroupRepo 51 | tx Transaction 52 | cache Cache 53 | } 54 | 55 | func NewUserGroupUseCase(c *conf.Bootstrap, repo UserGroupRepo, tx Transaction, cache Cache) *UserGroupUseCase { 56 | return &UserGroupUseCase{ 57 | c: c, 58 | repo: repo, 59 | tx: tx, 60 | cache: cache.WithPrefix("group"), // not use user_group since user group cache will be flush when flush user cache 61 | } 62 | } 63 | 64 | func (uc *UserGroupUseCase) Create(ctx context.Context, item *UserGroup) error { 65 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 66 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 67 | return uc.repo.Create(ctx, item) 68 | }) 69 | }) 70 | } 71 | 72 | func (uc *UserGroupUseCase) Find(ctx context.Context, condition *FindUserGroup) (rp []UserGroup, err error) { 73 | action := strings.Join([]string{"find", utils.StructMd5(condition)}, "_") 74 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 75 | return uc.find(ctx, action, condition) 76 | }) 77 | if err != nil { 78 | return 79 | } 80 | var cache FindUserGroupCache 81 | utils.JSON2Struct(&cache, str) 82 | condition.Page = cache.Page 83 | rp = cache.List 84 | return 85 | } 86 | 87 | func (uc *UserGroupUseCase) find(ctx context.Context, action string, condition *FindUserGroup) (res string, err error) { 88 | // read data from db and write to cache 89 | list := uc.repo.Find(ctx, condition) 90 | var cache FindUserGroupCache 91 | cache.List = list 92 | cache.Page = condition.Page 93 | res = utils.Struct2JSON(cache) 94 | uc.cache.Set(ctx, action, res, len(list) == 0) 95 | return 96 | } 97 | 98 | func (uc *UserGroupUseCase) Update(ctx context.Context, item *UpdateUserGroup) error { 99 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 100 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 101 | err = uc.repo.Update(ctx, item) 102 | return 103 | }) 104 | }) 105 | } 106 | 107 | func (uc *UserGroupUseCase) Delete(ctx context.Context, ids ...uint64) error { 108 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 109 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 110 | err = uc.repo.Delete(ctx, ids...) 111 | return 112 | }) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /internal/biz/whitelist.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/conf" 8 | "github.com/go-cinch/common/page" 9 | "github.com/go-cinch/common/utils" 10 | ) 11 | 12 | const ( 13 | WhitelistPermissionCategory uint32 = iota 14 | WhitelistJwtCategory 15 | ) 16 | 17 | type Whitelist struct { 18 | Id uint64 `json:"id,string"` 19 | Category uint32 `json:"category"` 20 | Resource string `json:"resource"` 21 | } 22 | 23 | type FindWhitelist struct { 24 | Page page.Page `json:"page"` 25 | Category *uint32 `json:"category"` 26 | Resource *string `json:"resource"` 27 | } 28 | 29 | type HasWhitelist struct { 30 | Category uint32 `json:"category"` 31 | Permission *CheckPermission `json:"permission"` 32 | } 33 | 34 | type FindWhitelistCache struct { 35 | Page page.Page `json:"page"` 36 | List []Whitelist `json:"list"` 37 | } 38 | 39 | type HasWhitelistCache struct { 40 | Has bool `json:"has"` 41 | } 42 | 43 | type UpdateWhitelist struct { 44 | Id uint64 `json:"id,string"` 45 | Category *uint32 `json:"category,omitempty"` 46 | Resource *string `json:"resource,omitempty"` 47 | } 48 | 49 | type WhitelistRepo interface { 50 | Create(ctx context.Context, item *Whitelist) error 51 | Find(ctx context.Context, condition *FindWhitelist) []Whitelist 52 | Has(ctx context.Context, condition *HasWhitelist) bool 53 | Update(ctx context.Context, item *UpdateWhitelist) error 54 | Delete(ctx context.Context, ids ...uint64) error 55 | } 56 | 57 | type WhitelistUseCase struct { 58 | c *conf.Bootstrap 59 | repo WhitelistRepo 60 | actionRepo WhitelistRepo 61 | tx Transaction 62 | cache Cache 63 | } 64 | 65 | func NewWhitelistUseCase(c *conf.Bootstrap, repo WhitelistRepo, tx Transaction, cache Cache) *WhitelistUseCase { 66 | return &WhitelistUseCase{ 67 | c: c, 68 | repo: repo, 69 | tx: tx, 70 | cache: cache.WithPrefix("whitelist"), 71 | } 72 | } 73 | 74 | func (uc *WhitelistUseCase) Create(ctx context.Context, item *Whitelist) error { 75 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 76 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 77 | return uc.repo.Create(ctx, item) 78 | }) 79 | }) 80 | } 81 | 82 | func (uc *WhitelistUseCase) Find(ctx context.Context, condition *FindWhitelist) (rp []Whitelist) { 83 | action := strings.Join([]string{"find", utils.StructMd5(condition)}, "_") 84 | str, err := uc.cache.Get(ctx, action, func(ctx context.Context) (string, error) { 85 | return uc.find(ctx, action, condition) 86 | }) 87 | if err != nil { 88 | return 89 | } 90 | var cache FindWhitelistCache 91 | utils.JSON2Struct(&cache, str) 92 | condition.Page = cache.Page 93 | rp = cache.List 94 | return 95 | } 96 | 97 | func (uc *WhitelistUseCase) find(ctx context.Context, action string, condition *FindWhitelist) (res string, err error) { 98 | // read data from db and write to cache 99 | list := uc.repo.Find(ctx, condition) 100 | var cache FindWhitelistCache 101 | cache.List = list 102 | cache.Page = condition.Page 103 | res = utils.Struct2JSON(cache) 104 | uc.cache.Set(ctx, action, res, len(list) == 0) 105 | return 106 | } 107 | 108 | func (uc *WhitelistUseCase) Has(ctx context.Context, condition *HasWhitelist) (rp bool) { 109 | return uc.repo.Has(ctx, condition) 110 | } 111 | 112 | func (uc *WhitelistUseCase) Update(ctx context.Context, item *UpdateWhitelist) error { 113 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 114 | return uc.cache.Flush(ctx, func(ctx context.Context) error { 115 | return uc.repo.Update(ctx, item) 116 | }) 117 | }) 118 | } 119 | 120 | func (uc *WhitelistUseCase) Delete(ctx context.Context, ids ...uint64) error { 121 | return uc.tx.Tx(ctx, func(ctx context.Context) error { 122 | return uc.cache.Flush(ctx, func(ctx context.Context) (err error) { 123 | err = uc.repo.Delete(ctx, ids...) 124 | return 125 | }) 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /internal/conf/conf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cinch.conf; 3 | 4 | option go_package = "auth/internal/conf;conf"; 5 | 6 | import "google/protobuf/duration.proto"; 7 | 8 | message Bootstrap { 9 | string name = 1; 10 | string version = 2; 11 | Server server = 3; 12 | Data data = 4; 13 | Tracer tracer = 5; 14 | Task task = 6; 15 | Client client = 7; 16 | Log log = 8; 17 | Hotspot hotspot = 9; 18 | } 19 | 20 | message Server { 21 | message HTTP { 22 | string network = 1; 23 | string addr = 2; 24 | google.protobuf.Duration timeout = 3; 25 | } 26 | message GRPC { 27 | string network = 1; 28 | string addr = 2; 29 | google.protobuf.Duration timeout = 3; 30 | } 31 | message Jwt { 32 | bool enable = 1; 33 | string key = 2; 34 | string expires = 3; 35 | } 36 | bool prod = 1; 37 | string machineId = 2; 38 | HTTP http = 3; 39 | GRPC grpc = 4; 40 | string language = 5; 41 | Jwt jwt = 6; 42 | bool permission = 7; 43 | bool idempotent = 8; 44 | bool validate = 9; 45 | bool nocache = 10; 46 | } 47 | 48 | message Data { 49 | message Database { 50 | string dsn = 1; 51 | string driver = 2; 52 | string endpoint = 3; 53 | string username = 4; 54 | string password = 5; 55 | string schema = 6; 56 | string query = 7; 57 | map tenants = 8; 58 | } 59 | message Redis { 60 | string dsn = 1; 61 | } 62 | Database database = 1; 63 | Redis redis = 2; 64 | } 65 | 66 | message Log { 67 | string level = 1; 68 | bool JSON = 2; 69 | bool showSQL = 3; 70 | } 71 | 72 | message Tracer { 73 | message Otlp { 74 | string endpoint = 1; 75 | bool insecure = 2; 76 | } 77 | message Stdout { 78 | bool prettyPrint = 1; 79 | } 80 | bool enable = 1; 81 | float ratio = 2; 82 | Otlp otlp = 3; 83 | Stdout stdout = 4; 84 | } 85 | 86 | message Client { 87 | bool health = 1; 88 | google.protobuf.Duration timeout = 2; 89 | string auth = 3; 90 | } 91 | 92 | message CronTask { 93 | string name = 1; 94 | string expr = 2; 95 | int64 timeout = 3; 96 | int64 retry = 4; 97 | } 98 | 99 | message Task { 100 | map cron = 1; 101 | message Group { 102 | string loginFailed = 1; 103 | string loginLast = 2; 104 | string refreshHotspot = 3; 105 | string refreshHotspotManual = 4; 106 | } 107 | Group group = 2; 108 | } 109 | 110 | message Hotspot { 111 | string name = 1; 112 | google.protobuf.Duration expire = 2; 113 | } -------------------------------------------------------------------------------- /internal/data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | -------------------------------------------------------------------------------- /internal/data/action.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/biz" 8 | "auth/internal/conf" 9 | "auth/internal/data/model" 10 | "auth/internal/data/query" 11 | "github.com/go-cinch/common/constant" 12 | "github.com/go-cinch/common/copierx" 13 | "github.com/go-cinch/common/id" 14 | "github.com/go-cinch/common/log" 15 | "github.com/go-cinch/common/utils" 16 | "github.com/gobwas/glob" 17 | "gorm.io/gen" 18 | ) 19 | 20 | type actionRepo struct { 21 | c *conf.Bootstrap 22 | data *Data 23 | hotspot biz.HotspotRepo 24 | } 25 | 26 | func NewActionRepo(c *conf.Bootstrap, data *Data, hotspot biz.HotspotRepo) biz.ActionRepo { 27 | return &actionRepo{ 28 | c: c, 29 | data: data, 30 | hotspot: hotspot, 31 | } 32 | } 33 | 34 | func (ro actionRepo) Create(ctx context.Context, item *biz.Action) (err error) { 35 | ok := ro.WordExists(ctx, item.Word) 36 | if ok { 37 | err = biz.ErrDuplicateField(ctx, "word", item.Word) 38 | return 39 | } 40 | var m model.Action 41 | copierx.Copy(&m, item) 42 | p := query.Use(ro.data.DB(ctx)).Action 43 | db := p.WithContext(ctx) 44 | m.ID = ro.data.ID(ctx) 45 | m.Code = id.NewCode(m.ID) 46 | if m.Resource == "" { 47 | m.Resource = "*" 48 | } 49 | err = db.Create(&m) 50 | return 51 | } 52 | 53 | func (ro actionRepo) GetDefault(ctx context.Context) (rp biz.Action) { 54 | p := query.Use(ro.data.DB(ctx)).Action 55 | db := p.WithContext(ctx) 56 | m := db.GetByCol(p.Word.ColumnName().String(), "default") 57 | copierx.Copy(&rp, m) 58 | return 59 | } 60 | 61 | func (ro actionRepo) Find(ctx context.Context, condition *biz.FindAction) (rp []biz.Action) { 62 | p := query.Use(ro.data.DB(ctx)).Action 63 | db := p.WithContext(ctx) 64 | rp = make([]biz.Action, 0) 65 | list := make([]model.Action, 0) 66 | conditions := make([]gen.Condition, 0, 2) 67 | if condition.Name != nil { 68 | conditions = append(conditions, p.Name.Like(strings.Join([]string{"%", *condition.Name, "%"}, ""))) 69 | } 70 | if condition.Code != nil { 71 | conditions = append(conditions, p.Code.Like(strings.Join([]string{"%", *condition.Code, "%"}, ""))) 72 | } 73 | if condition.Word != nil { 74 | conditions = append(conditions, p.Word.Like(strings.Join([]string{"%", *condition.Word, "%"}, ""))) 75 | } 76 | if condition.Resource != nil { 77 | conditions = append(conditions, p.Resource.Like(strings.Join([]string{"%", *condition.Resource, "%"}, ""))) 78 | } 79 | condition.Page.Primary = p.ID.ColumnName().String() 80 | condition.Page. 81 | WithContext(ctx). 82 | Query( 83 | db. 84 | Order(p.ID.Desc()). 85 | Where(conditions...). 86 | UnderlyingDB(), 87 | ). 88 | Find(&list) 89 | copierx.Copy(&rp, list) 90 | return 91 | } 92 | 93 | func (ro actionRepo) FindByCode(ctx context.Context, code string) (rp []biz.Action) { 94 | rp = make([]biz.Action, 0) 95 | if code == "" { 96 | return 97 | } 98 | list := make([]*model.Action, 0) 99 | p := query.Use(ro.data.DB(ctx)).Action 100 | db := p.WithContext(ctx) 101 | arr := strings.Split(code, ",") 102 | list, _ = db. 103 | Where(p.Code.In(arr...)). 104 | Find() 105 | copierx.Copy(&rp, list) 106 | return 107 | } 108 | 109 | func (ro actionRepo) Update(ctx context.Context, item *biz.UpdateAction) (err error) { 110 | p := query.Use(ro.data.DB(ctx)).Action 111 | db := p.WithContext(ctx) 112 | m := db.GetByID(item.Id) 113 | if m.ID == constant.UI0 { 114 | err = biz.ErrRecordNotFound(ctx) 115 | return 116 | } 117 | change := make(map[string]interface{}) 118 | utils.CompareDiff(m, item, &change) 119 | if len(change) == 0 { 120 | err = biz.ErrDataNotChange(ctx) 121 | return 122 | } 123 | if item.Word != nil && *item.Word != m.Word { 124 | ok := ro.WordExists(ctx, *item.Word) 125 | if ok { 126 | err = biz.ErrDuplicateField(ctx, p.Word.ColumnName().String(), *item.Word) 127 | return 128 | } 129 | } 130 | _, err = db. 131 | Where(p.ID.Eq(item.Id)). 132 | Updates(&change) 133 | return 134 | } 135 | 136 | func (ro actionRepo) Delete(ctx context.Context, ids ...uint64) (err error) { 137 | p := query.Use(ro.data.DB(ctx)).Action 138 | db := p.WithContext(ctx) 139 | _, err = db. 140 | Where(p.ID.In(ids...)). 141 | Delete() 142 | if err != nil { 143 | return 144 | } 145 | count, _ := db.Count() 146 | if count == 0 { 147 | err = biz.ErrKeepLeastOneAction(ctx) 148 | } 149 | return 150 | } 151 | 152 | func (ro actionRepo) CodeExists(ctx context.Context, code string) (err error) { 153 | p := query.Use(ro.data.DB(ctx)).Action 154 | db := p.WithContext(ctx) 155 | arr := strings.Split(code, ",") 156 | for _, item := range arr { 157 | m := db.GetByCol(p.Code.ColumnName().String(), item) 158 | if m.ID == constant.UI0 { 159 | err = biz.ErrRecordNotFound(ctx) 160 | log. 161 | WithContext(ctx). 162 | WithError(err). 163 | Error("invalid code: %s", code) 164 | return 165 | } 166 | } 167 | return 168 | } 169 | 170 | func (ro actionRepo) WordExists(ctx context.Context, word string) (ok bool) { 171 | p := query.Use(ro.data.DB(ctx)).Action 172 | db := p.WithContext(ctx) 173 | arr := strings.Split(word, ",") 174 | for _, item := range arr { 175 | m := db.GetByCol(p.Word.ColumnName().String(), item) 176 | if m.ID == constant.UI0 { 177 | log. 178 | WithContext(ctx). 179 | Error("invalid word: %s", item) 180 | return 181 | } 182 | } 183 | ok = true 184 | return 185 | } 186 | 187 | func (ro actionRepo) Permission(ctx context.Context, code string, req *biz.CheckPermission) (pass bool) { 188 | arr := strings.Split(code, ",") 189 | for _, item := range arr { 190 | pass = ro.permission(ctx, item, req) 191 | if pass { 192 | return 193 | } 194 | } 195 | return 196 | } 197 | 198 | func (ro actionRepo) permission(ctx context.Context, code string, req *biz.CheckPermission) (pass bool) { 199 | if code == "" { 200 | return 201 | } 202 | action := ro.hotspot.GetActionByCode(ctx, code) 203 | return ro.MatchResource(ctx, action.Resource, req) 204 | } 205 | 206 | func (actionRepo) MatchResource(_ context.Context, resource string, req *biz.CheckPermission) (pass bool) { 207 | if resource == "" { 208 | // empty resource no need match 209 | return 210 | } 211 | arr1 := strings.Split(resource, "\n") 212 | for _, v1 := range arr1 { 213 | if v1 == "*" { 214 | pass = true 215 | return 216 | } 217 | arr2 := strings.Split(v1, "|") 218 | switch len(arr2) { 219 | case 1: 220 | // only grpc resource 221 | if req.Resource == arr2[0] { 222 | pass = true 223 | return 224 | } 225 | case 2: 226 | // only http method / http uri 227 | methods := strings.Split(arr2[0], ",") 228 | g, err := glob.Compile(arr2[1]) 229 | if err != nil { 230 | return 231 | } 232 | matched := g.Match(req.URI) 233 | if matched && utils.Contains[string](methods, req.Method) { 234 | pass = true 235 | return 236 | } 237 | case 3: 238 | // grpc resource / http method / http uri 239 | // match one means has permission 240 | if req.Resource == arr2[2] { 241 | pass = true 242 | return 243 | } 244 | methods := strings.Split(arr2[0], ",") 245 | g, err := glob.Compile(arr2[1]) 246 | if err != nil { 247 | return 248 | } 249 | matched := g.Match(req.URI) 250 | if matched && utils.Contains[string](methods, req.Method) { 251 | pass = true 252 | return 253 | } 254 | } 255 | } 256 | return 257 | } 258 | -------------------------------------------------------------------------------- /internal/data/cache.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math/rand" 7 | "strings" 8 | "time" 9 | 10 | "auth/internal/biz" 11 | "auth/internal/conf" 12 | "github.com/bsm/redislock" 13 | "github.com/go-cinch/common/log" 14 | "github.com/go-cinch/common/plugins/gorm/tenant" 15 | "github.com/patrickmn/go-cache" 16 | "github.com/redis/go-redis/v9" 17 | "go.opentelemetry.io/otel" 18 | "go.opentelemetry.io/otel/codes" 19 | ) 20 | 21 | // Cache . 22 | type Cache struct { 23 | redis redis.UniversalClient 24 | locker *redislock.Client 25 | disable bool 26 | prefix string 27 | lock string 28 | val string 29 | refresh bool 30 | } 31 | 32 | var local = cache.New(30*time.Minute, 60*time.Minute) 33 | 34 | // NewCache . 35 | func NewCache(c *conf.Bootstrap, client redis.UniversalClient) biz.Cache { 36 | return &Cache{ 37 | redis: client, 38 | locker: redislock.New(client), 39 | disable: c.Server.Nocache, 40 | lock: "lock", 41 | val: "val", 42 | } 43 | } 44 | 45 | func (c *Cache) Cache() redis.UniversalClient { 46 | return c.redis 47 | } 48 | 49 | func (c *Cache) WithPrefix(prefix string) biz.Cache { 50 | return &Cache{ 51 | redis: c.redis, 52 | locker: c.locker, 53 | disable: c.disable, 54 | prefix: prefix, 55 | lock: c.lock, 56 | val: c.val, 57 | } 58 | } 59 | 60 | func (c *Cache) WithRefresh() biz.Cache { 61 | return &Cache{ 62 | redis: c.redis, 63 | locker: c.locker, 64 | disable: c.disable, 65 | prefix: c.prefix, 66 | lock: c.lock, 67 | val: c.val, 68 | refresh: true, 69 | } 70 | } 71 | 72 | func (c *Cache) Get( 73 | ctx context.Context, 74 | action string, 75 | write func(context.Context) (string, error), 76 | ) (res string, err error) { 77 | tr := otel.Tracer("cache") 78 | ctx, span := tr.Start(ctx, "Get") 79 | defer span.End() 80 | if c.disable { 81 | return write(ctx) 82 | } 83 | key := c.getValKey(ctx, action) 84 | if !c.refresh { 85 | // 1. first get cache 86 | // 1.1. get from local 87 | res, err = GetFromLocal(key) 88 | if err == nil { 89 | // cache exists 90 | return 91 | } 92 | // 1.2. get from redis 93 | res, err = c.redis.Get(ctx, key).Result() 94 | if err == nil { 95 | // cache exists 96 | return 97 | } 98 | } 99 | // 2. get lock before read db 100 | lock, err := c.Lock(ctx, action) 101 | if err != nil { 102 | err = biz.ErrTooManyRequests(ctx) 103 | return 104 | } 105 | defer func() { 106 | _ = lock.Release(ctx) 107 | }() 108 | if !c.refresh { 109 | // 3. double check cache exists(avoid concurrency step 1 ok=false) 110 | res, err = GetFromLocal(key) 111 | if err == nil { 112 | // cache exists 113 | return 114 | } 115 | res, err = c.redis.Get(ctx, key).Result() 116 | if err == nil { 117 | // cache exists 118 | return 119 | } 120 | } 121 | // 4. load data from db and write to cache 122 | if write != nil { 123 | res, err = write(ctx) 124 | } 125 | return 126 | } 127 | 128 | func (c *Cache) Set(ctx context.Context, action, data string, short bool) { 129 | // set random expiration avoid a large number of keys expire at the same time 130 | seconds := rand.New(rand.NewSource(time.Now().Unix())).Int63n(300) + 300 131 | if short { 132 | // if record not found, set a short expiration 133 | seconds = 60 134 | } 135 | c.SetWithExpiration(ctx, action, data, seconds) 136 | } 137 | 138 | func (c *Cache) SetWithExpiration(ctx context.Context, action, data string, seconds int64) { 139 | if c.disable { 140 | return 141 | } 142 | key := c.getValKey(ctx, action) 143 | // set to local cache 144 | Set2Local(key, data, int(seconds)) 145 | // set to redis 146 | err := c.redis.Set(ctx, key, data, time.Duration(seconds)*time.Second).Err() 147 | if err != nil { 148 | log. 149 | WithContext(ctx). 150 | WithError(err). 151 | WithFields(log.Fields{ 152 | "action": action, 153 | "seconds": seconds, 154 | }). 155 | Warn("set cache failed") 156 | return 157 | } 158 | } 159 | 160 | func (c *Cache) Del(ctx context.Context, action string) { 161 | if c.disable { 162 | return 163 | } 164 | key := c.getValKey(ctx, action) 165 | DelFromLocal(key) 166 | err := c.redis.Del(ctx, key).Err() 167 | if err != nil { 168 | log. 169 | WithContext(ctx). 170 | WithError(err). 171 | WithFields(log.Fields{ 172 | "action": action, 173 | "key": key, 174 | }). 175 | Warn("del cache failed") 176 | } 177 | } 178 | 179 | func (c *Cache) Flush(ctx context.Context, handler func(ctx context.Context) error) (err error) { 180 | err = handler(ctx) 181 | if err != nil { 182 | return 183 | } 184 | if c.disable { 185 | return 186 | } 187 | action := c.getPrefixKey(ctx) 188 | arr := c.redis.Keys(ctx, action).Val() 189 | p := c.redis.Pipeline() 190 | for _, item := range arr { 191 | if item == c.lock { 192 | continue 193 | } 194 | DelFromLocal(item) 195 | p.Del(ctx, item) 196 | } 197 | _, pErr := p.Exec(ctx) 198 | if pErr != nil { 199 | log. 200 | WithContext(ctx). 201 | WithError(pErr). 202 | WithFields(log.Fields{ 203 | "action": action, 204 | }). 205 | Warn("flush cache failed") 206 | } 207 | return 208 | } 209 | 210 | func (c *Cache) FlushByPrefix(ctx context.Context, prefix ...string) (err error) { 211 | action := c.getPrefixKey(ctx, prefix...) 212 | arr := c.redis.Keys(ctx, action).Val() 213 | p := c.redis.Pipeline() 214 | for _, item := range arr { 215 | if item == c.lock { 216 | continue 217 | } 218 | DelFromLocal(item) 219 | p.Del(ctx, item) 220 | } 221 | _, pErr := p.Exec(ctx) 222 | if pErr != nil { 223 | log. 224 | WithContext(ctx). 225 | WithError(pErr). 226 | WithFields(log.Fields{ 227 | "action": action, 228 | }). 229 | Warn("flush cache by prefix failed") 230 | } 231 | return 232 | } 233 | 234 | func (c *Cache) Lock(ctx context.Context, action string) (*redislock.Lock, error) { 235 | tr := otel.Tracer("cache") 236 | ctx, span := tr.Start(ctx, "Lock") 237 | defer span.End() 238 | lock, err := c.locker.Obtain( 239 | ctx, 240 | c.getLockKey(ctx, action), 241 | 20*time.Second, 242 | &redislock.Options{ 243 | RetryStrategy: redislock.LimitRetry(redislock.LinearBackoff(5*time.Millisecond), 400), 244 | }, 245 | ) 246 | if err != nil { 247 | span.SetStatus(codes.Error, err.Error()) 248 | } 249 | return lock, err 250 | } 251 | 252 | func Set2Local(key, val string, expire int) { 253 | local.Set(key, val, time.Duration(expire)*time.Second) 254 | } 255 | 256 | func GetFromLocal(key string) (string, error) { 257 | val, ok := local.Get(key) 258 | if !ok { 259 | return "", errors.New("key not found") 260 | } 261 | return val.(string), nil 262 | } 263 | 264 | func DelFromLocal(key string) { 265 | local.Delete(key) 266 | } 267 | 268 | func (c *Cache) getPrefixKey(ctx context.Context, arr ...string) string { 269 | id := tenant.FromContext(ctx) 270 | prefix := c.prefix 271 | if len(arr) > 0 { 272 | // append params prefix need add val 273 | prefix = strings.Join(append([]string{prefix, c.val}, arr...), "_") 274 | } 275 | if strings.TrimSpace(prefix) == "" { 276 | // avoid flush all key 277 | log. 278 | WithContext(ctx). 279 | Warn("invalid prefix") 280 | prefix = "prefix" 281 | } 282 | if id == "" { 283 | return strings.Join([]string{prefix, "*"}, "") 284 | } 285 | return strings.Join([]string{id, "_", prefix, "*"}, "") 286 | } 287 | 288 | func (c *Cache) getValKey(ctx context.Context, action string) string { 289 | id := tenant.FromContext(ctx) 290 | if id == "" { 291 | return strings.Join([]string{c.prefix, c.val, action}, "_") 292 | } 293 | return strings.Join([]string{id, c.prefix, c.val, action}, "_") 294 | } 295 | 296 | func (c *Cache) getLockKey(ctx context.Context, action string) string { 297 | id := tenant.FromContext(ctx) 298 | if id == "" { 299 | return strings.Join([]string{c.prefix, c.lock, action}, "_") 300 | } 301 | return strings.Join([]string{id, c.prefix, c.lock, action}, "_") 302 | } 303 | -------------------------------------------------------------------------------- /internal/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | "time" 9 | 10 | "auth/internal/biz" 11 | "auth/internal/conf" 12 | "auth/internal/db" 13 | "github.com/go-cinch/common/id" 14 | "github.com/go-cinch/common/log" 15 | glog "github.com/go-cinch/common/plugins/gorm/log" 16 | "github.com/go-cinch/common/plugins/gorm/tenant" 17 | "github.com/go-cinch/common/utils" 18 | "github.com/google/wire" 19 | "github.com/pkg/errors" 20 | "github.com/redis/go-redis/v9" 21 | "go.opentelemetry.io/otel/sdk/trace" 22 | "gorm.io/gorm" 23 | "gorm.io/gorm/schema" 24 | ) 25 | 26 | // ProviderSet is data providers. 27 | var ProviderSet = wire.NewSet( 28 | NewRedis, NewDB, NewSonyflake, NewTracer, NewData, NewTransaction, NewCache, 29 | NewUserRepo, 30 | NewActionRepo, 31 | NewRoleRepo, 32 | NewUserGroupRepo, 33 | NewPermissionRepo, 34 | NewWhitelistRepo, 35 | NewHotspotRepo, 36 | ) 37 | 38 | // Data . 39 | type Data struct { 40 | tenant *tenant.Tenant 41 | redis redis.UniversalClient 42 | sonyflake *id.Sonyflake 43 | } 44 | 45 | // NewData . 46 | func NewData(redis redis.UniversalClient, gormTenant *tenant.Tenant, sonyflake *id.Sonyflake, tp *trace.TracerProvider) (d *Data, cleanup func()) { 47 | d = &Data{ 48 | redis: redis, 49 | tenant: gormTenant, 50 | sonyflake: sonyflake, 51 | } 52 | cleanup = func() { 53 | if tp != nil { 54 | tp.Shutdown(context.Background()) 55 | } 56 | log.Info("clean data") 57 | } 58 | return 59 | } 60 | 61 | type contextTxKey struct{} 62 | 63 | // Tx is transaction wrapper 64 | func (d *Data) Tx(ctx context.Context, handler func(ctx context.Context) error) error { 65 | return d.tenant.DB(ctx).Transaction(func(tx *gorm.DB) error { 66 | ctx = context.WithValue(ctx, contextTxKey{}, tx) 67 | return handler(ctx) 68 | }) 69 | } 70 | 71 | // DB can get tx from ctx, if not exist return db 72 | func (d *Data) DB(ctx context.Context) *gorm.DB { 73 | tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB) 74 | if ok { 75 | return tx 76 | } 77 | return d.tenant.DB(ctx) 78 | } 79 | 80 | // HiddenSQL return a hidden sql ctx 81 | func (*Data) HiddenSQL(ctx context.Context) context.Context { 82 | ctx = glog.NewHiddenSQLContext(ctx) 83 | return ctx 84 | } 85 | 86 | // Cache can get cache instance 87 | func (d *Data) Cache() redis.UniversalClient { 88 | return d.redis 89 | } 90 | 91 | // ID can get unique id 92 | func (d *Data) ID(ctx context.Context) uint64 { 93 | return d.sonyflake.ID(ctx) 94 | } 95 | 96 | // NewTransaction . 97 | func NewTransaction(d *Data) biz.Transaction { 98 | return d 99 | } 100 | 101 | // NewRedis is initialize redis connection from config 102 | func NewRedis(c *conf.Bootstrap) (client redis.UniversalClient, err error) { 103 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 104 | defer cancel() 105 | var u *url.URL 106 | u, err = url.Parse(c.Data.Redis.Dsn) 107 | if err != nil { 108 | log.Error(err) 109 | err = errors.New("initialize redis failed") 110 | return 111 | } 112 | u.User = url.UserPassword(u.User.Username(), "***") 113 | showDsn, _ := url.PathUnescape(u.String()) 114 | client, err = utils.ParseRedisURI(c.Data.Redis.Dsn) 115 | if err != nil { 116 | log.Error(err) 117 | err = errors.New("initialize redis failed") 118 | return 119 | } 120 | err = client.Ping(ctx).Err() 121 | if err != nil { 122 | log.Error(err) 123 | err = errors.New("initialize redis failed") 124 | return 125 | } 126 | log. 127 | WithField("redis.dsn", showDsn). 128 | Info("initialize redis success") 129 | return 130 | } 131 | 132 | // NewDB is initialize db connection from config 133 | func NewDB(c *conf.Bootstrap) (gormTenant *tenant.Tenant, err error) { 134 | ops := make([]func(*tenant.Options), 0, len(c.Data.Database.Tenants)+3) 135 | if len(c.Data.Database.Tenants) > 0 { 136 | for k, v := range c.Data.Database.Tenants { 137 | ops = append(ops, tenant.WithDSN(k, v)) 138 | } 139 | } else { 140 | dsn := c.Data.Database.Dsn 141 | if dsn == "" { 142 | dsn = fmt.Sprintf( 143 | "%s:%s@tcp(%s)/%s?%s", 144 | c.Data.Database.Username, 145 | c.Data.Database.Password, 146 | c.Data.Database.Endpoint, 147 | c.Data.Database.Schema, 148 | c.Data.Database.Query, 149 | ) 150 | } 151 | ops = append(ops, tenant.WithDSN("", dsn)) 152 | } 153 | ops = append(ops, tenant.WithSQLFile(db.SQLFiles)) 154 | ops = append(ops, tenant.WithSQLRoot(db.SQLRoot)) 155 | 156 | level := log.NewLevel(c.Log.Level) 157 | // force to warn level when show sql is false 158 | if level > log.WarnLevel && !c.Log.ShowSQL { 159 | level = log.WarnLevel 160 | } 161 | ops = append(ops, tenant.WithConfig(&gorm.Config{ 162 | NamingStrategy: schema.NamingStrategy{ 163 | SingularTable: true, 164 | }, 165 | QueryFields: true, 166 | Logger: glog.New( 167 | glog.WithColorful(false), 168 | glog.WithSlow(200), 169 | glog.WithLevel(level), 170 | ), 171 | })) 172 | 173 | gormTenant, err = tenant.New(ops...) 174 | if err != nil { 175 | log.Error(err) 176 | err = errors.New("initialize db failed") 177 | return 178 | } 179 | err = gormTenant.Migrate() 180 | if err != nil { 181 | log.Error(err) 182 | err = errors.New("initialize db failed") 183 | return 184 | } 185 | log.Info("initialize db success") 186 | return 187 | } 188 | 189 | // NewSonyflake is initialize sonyflake id generator 190 | func NewSonyflake(c *conf.Bootstrap) (sf *id.Sonyflake, err error) { 191 | machineID, _ := strconv.ParseUint(c.Server.MachineId, 10, 16) 192 | sf = id.NewSonyflake( 193 | id.WithSonyflakeMachineID(uint16(machineID)), 194 | id.WithSonyflakeStartTime(time.Date(100, 10, 10, 0, 0, 0, 0, time.UTC)), 195 | ) 196 | if sf.Error != nil { 197 | log.Error(sf.Error) 198 | err = errors.New("initialize sonyflake failed") 199 | return 200 | } 201 | log. 202 | WithField("machine.id", machineID). 203 | Info("initialize sonyflake success") 204 | return 205 | } 206 | -------------------------------------------------------------------------------- /internal/data/model/action.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | const TableNameAction = "action" 8 | 9 | // Action mapped from table 10 | type Action struct { 11 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 12 | Name string `gorm:"column:name;comment:name" json:"name"` // name 13 | Code string `gorm:"column:code;not null;comment:code" json:"code"` // code 14 | Word string `gorm:"column:word;comment:keyword, must be unique, used as frontend display" json:"word"` // keyword, must be unique, used as frontend display 15 | /* 16 | resource array, split by break line str, example: GET|/user+ 17 | +PUT,PATCH|/role/*+ 18 | +GET|/action 19 | */ 20 | Resource string `gorm:"column:resource;comment:resource array, split by break line str, example: GET|/user+\n+PUT,PATCH|/role/*+\n+GET|/action" json:"resource"` 21 | Menu string `gorm:"column:menu;comment:menu array, split by break line str" json:"menu"` // menu array, split by break line str 22 | Btn string `gorm:"column:btn;comment:btn array, split by break line str" json:"btn"` // btn array, split by break line str 23 | } 24 | 25 | // TableName Action's table name 26 | func (*Action) TableName() string { 27 | return TableNameAction 28 | } 29 | -------------------------------------------------------------------------------- /internal/data/model/role.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | const TableNameRole = "role" 8 | 9 | // Role mapped from table 10 | type Role struct { 11 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 12 | Name string `gorm:"column:name;comment:name" json:"name"` // name 13 | Word string `gorm:"column:word;comment:keyword, must be unique, used as frontend display" json:"word"` // keyword, must be unique, used as frontend display 14 | Action string `gorm:"column:action;comment:role action code array" json:"action"` // role action code array 15 | } 16 | 17 | // TableName Role's table name 18 | func (*Role) TableName() string { 19 | return TableNameRole 20 | } 21 | -------------------------------------------------------------------------------- /internal/data/model/user.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | import ( 8 | "github.com/golang-module/carbon/v2" 9 | ) 10 | 11 | const TableNameUser = "user" 12 | 13 | // User mapped from table 14 | type User struct { 15 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 16 | CreatedAt carbon.DateTime `gorm:"column:created_at;comment:create time" json:"createdAt"` // create time 17 | UpdatedAt carbon.DateTime `gorm:"column:updated_at;comment:update time" json:"updatedAt"` // update time 18 | RoleID uint64 `gorm:"column:role_id;comment:role id" json:"roleId,string"` // role id 19 | Action string `gorm:"column:action;comment:user action code array" json:"action"` // user action code array 20 | Username string `gorm:"column:username;comment:user login name" json:"username"` // user login name 21 | Code string `gorm:"column:code;not null;comment:user code" json:"code"` // user code 22 | Password string `gorm:"column:password;comment:password" json:"password"` // password 23 | Platform string `gorm:"column:platform;comment:device platform: pc/android/ios/mini..." json:"platform"` // device platform: pc/android/ios/mini... 24 | LastLogin carbon.DateTime `gorm:"column:last_login;comment:last login time" json:"lastLogin"` // last login time 25 | Locked bool `gorm:"column:locked;comment:locked(0: unlock, 1: locked)" json:"locked"` // locked(0: unlock, 1: locked) 26 | LockExpire uint64 `gorm:"column:lock_expire;comment:lock expiration time" json:"lockExpire"` // lock expiration time 27 | Wrong uint64 `gorm:"column:wrong;comment:type wrong password count" json:"wrong"` // type wrong password count 28 | Role Role `gorm:"foreignKey:RoleID" json:"role"` 29 | } 30 | 31 | // TableName User's table name 32 | func (*User) TableName() string { 33 | return TableNameUser 34 | } 35 | -------------------------------------------------------------------------------- /internal/data/model/user_group.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | const TableNameUserGroup = "user_group" 8 | 9 | // UserGroup mapped from table 10 | type UserGroup struct { 11 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 12 | Name string `gorm:"column:name;comment:name" json:"name"` // name 13 | Word string `gorm:"column:word;comment:keyword, must be unique, used as frontend display" json:"word"` // keyword, must be unique, used as frontend display 14 | Action string `gorm:"column:action;comment:user group action code array" json:"action"` // user group action code array 15 | Users []User `gorm:"many2many:user_user_group_relation" json:"users"` 16 | } 17 | 18 | // TableName UserGroup's table name 19 | func (*UserGroup) TableName() string { 20 | return TableNameUserGroup 21 | } 22 | -------------------------------------------------------------------------------- /internal/data/model/user_user_group_relation.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | const TableNameUserUserGroupRelation = "user_user_group_relation" 8 | 9 | // UserUserGroupRelation mapped from table 10 | type UserUserGroupRelation struct { 11 | UserID uint64 `gorm:"column:user_id;primaryKey;comment:auto increment id" json:"userId,string"` // auto increment id 12 | UserGroupID uint64 `gorm:"column:user_group_id;primaryKey;comment:auto increment id" json:"userGroupId,string"` // auto increment id 13 | } 14 | 15 | // TableName UserUserGroupRelation's table name 16 | func (*UserUserGroupRelation) TableName() string { 17 | return TableNameUserUserGroupRelation 18 | } 19 | -------------------------------------------------------------------------------- /internal/data/model/whitelist.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package model 6 | 7 | const TableNameWhitelist = "whitelist" 8 | 9 | // Whitelist mapped from table 10 | type Whitelist struct { 11 | ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:auto increment id" json:"id,string"` // auto increment id 12 | Category uint32 `gorm:"column:category;not null;comment:category(0:permission, 1:jwt)" json:"category"` // category(0:permission, 1:jwt) 13 | /* 14 | resource array, split by break line str, example: GET|/user+ 15 | +PUT,PATCH|/role/*+ 16 | +GET|/action 17 | */ 18 | Resource string `gorm:"column:resource;not null;comment:resource array, split by break line str, example: GET|/user+\n+PUT,PATCH|/role/*+\n+GET|/action" json:"resource"` 19 | } 20 | 21 | // TableName Whitelist's table name 22 | func (*Whitelist) TableName() string { 23 | return TableNameWhitelist 24 | } 25 | -------------------------------------------------------------------------------- /internal/data/permission.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/biz" 8 | "github.com/samber/lo" 9 | ) 10 | 11 | type permissionRepo struct { 12 | data *Data 13 | action biz.ActionRepo 14 | hotspot biz.HotspotRepo 15 | } 16 | 17 | // NewPermissionRepo . 18 | func NewPermissionRepo(data *Data, action biz.ActionRepo, hotspot biz.HotspotRepo) biz.PermissionRepo { 19 | return &permissionRepo{ 20 | data: data, 21 | action: action, 22 | hotspot: hotspot, 23 | } 24 | } 25 | 26 | func (ro permissionRepo) Check(ctx context.Context, item *biz.CheckPermission) (pass bool) { 27 | user := ro.hotspot.GetUserByCode(ctx, item.UserCode) 28 | // 1. check default permission 29 | defaultAction := ro.hotspot.GetActionByWord(ctx, "default") 30 | pass = ro.action.Permission(ctx, defaultAction.Code, item) 31 | if pass { 32 | return 33 | } 34 | // 2. check user permission 35 | pass = ro.action.Permission(ctx, user.Action, item) 36 | if pass { 37 | return 38 | } 39 | // 3. check role permission 40 | pass = ro.action.Permission(ctx, user.Role.Action, item) 41 | if pass { 42 | return 43 | } 44 | // 4. check user group permission 45 | groups := ro.hotspot.FindUserGroupByUserCode(ctx, user.Code) 46 | for _, group := range groups { 47 | pass = ro.action.Permission(ctx, group.Action, item) 48 | if pass { 49 | return 50 | } 51 | } 52 | return 53 | } 54 | 55 | func (ro permissionRepo) GetByUserCode(ctx context.Context, code string) (rp *biz.Permission) { 56 | rp = &biz.Permission{} 57 | rp.Resources = make([]string, 0) 58 | user := ro.hotspot.GetUserByCode(ctx, code) 59 | // 1. user action 60 | actions := make([]string, 0) 61 | defaultAction := ro.hotspot.GetActionByWord(ctx, "default") 62 | actions = append(actions, defaultAction.Code) 63 | if user.Action != "" { 64 | arr := strings.Split(user.Action, ",") 65 | actions = append(actions, arr...) 66 | } 67 | // 2. role action 68 | if user.Role.Action != "" { 69 | arr := strings.Split(user.Role.Action, ",") 70 | actions = append(actions, arr...) 71 | } 72 | // 3. user group action 73 | groups := ro.hotspot.FindUserGroupByUserCode(ctx, user.Code) 74 | for _, group := range groups { 75 | if group.Action != "" { 76 | arr := strings.Split(group.Action, ",") 77 | actions = append(actions, arr...) 78 | } 79 | } 80 | actions = lo.Uniq(actions) 81 | if len(actions) > 0 { 82 | list := ro.hotspot.FindActionByCode(ctx, actions...) 83 | for _, item := range list { 84 | if item.Resource != "" { 85 | rp.Resources = append(rp.Resources, strings.Split(item.Resource, "\n")...) 86 | } 87 | if item.Menu != "" { 88 | rp.Menus = append(rp.Menus, strings.Split(item.Menu, "\n")...) 89 | } 90 | if item.Btn != "" { 91 | rp.Btns = append(rp.Btns, strings.Split(item.Btn, "\n")...) 92 | } 93 | } 94 | } 95 | rp.Resources = lo.Uniq(rp.Resources) 96 | rp.Menus = lo.Uniq(rp.Menus) 97 | rp.Btns = lo.Uniq(rp.Btns) 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /internal/data/query/gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package query 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | 11 | "gorm.io/gorm" 12 | 13 | "gorm.io/gen" 14 | 15 | "gorm.io/plugin/dbresolver" 16 | ) 17 | 18 | func Use(db *gorm.DB, opts ...gen.DOOption) *Query { 19 | return &Query{ 20 | db: db, 21 | Action: newAction(db, opts...), 22 | Role: newRole(db, opts...), 23 | User: newUser(db, opts...), 24 | UserGroup: newUserGroup(db, opts...), 25 | UserUserGroupRelation: newUserUserGroupRelation(db, opts...), 26 | Whitelist: newWhitelist(db, opts...), 27 | } 28 | } 29 | 30 | type Query struct { 31 | db *gorm.DB 32 | 33 | Action action 34 | Role role 35 | User user 36 | UserGroup userGroup 37 | UserUserGroupRelation userUserGroupRelation 38 | Whitelist whitelist 39 | } 40 | 41 | func (q *Query) Available() bool { return q.db != nil } 42 | 43 | func (q *Query) clone(db *gorm.DB) *Query { 44 | return &Query{ 45 | db: db, 46 | Action: q.Action.clone(db), 47 | Role: q.Role.clone(db), 48 | User: q.User.clone(db), 49 | UserGroup: q.UserGroup.clone(db), 50 | UserUserGroupRelation: q.UserUserGroupRelation.clone(db), 51 | Whitelist: q.Whitelist.clone(db), 52 | } 53 | } 54 | 55 | func (q *Query) ReadDB() *Query { 56 | return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) 57 | } 58 | 59 | func (q *Query) WriteDB() *Query { 60 | return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) 61 | } 62 | 63 | func (q *Query) ReplaceDB(db *gorm.DB) *Query { 64 | return &Query{ 65 | db: db, 66 | Action: q.Action.replaceDB(db), 67 | Role: q.Role.replaceDB(db), 68 | User: q.User.replaceDB(db), 69 | UserGroup: q.UserGroup.replaceDB(db), 70 | UserUserGroupRelation: q.UserUserGroupRelation.replaceDB(db), 71 | Whitelist: q.Whitelist.replaceDB(db), 72 | } 73 | } 74 | 75 | type queryCtx struct { 76 | Action *actionDo 77 | Role *roleDo 78 | User *userDo 79 | UserGroup *userGroupDo 80 | UserUserGroupRelation *userUserGroupRelationDo 81 | Whitelist *whitelistDo 82 | } 83 | 84 | func (q *Query) WithContext(ctx context.Context) *queryCtx { 85 | return &queryCtx{ 86 | Action: q.Action.WithContext(ctx), 87 | Role: q.Role.WithContext(ctx), 88 | User: q.User.WithContext(ctx), 89 | UserGroup: q.UserGroup.WithContext(ctx), 90 | UserUserGroupRelation: q.UserUserGroupRelation.WithContext(ctx), 91 | Whitelist: q.Whitelist.WithContext(ctx), 92 | } 93 | } 94 | 95 | func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { 96 | return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) 97 | } 98 | 99 | func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { 100 | tx := q.db.Begin(opts...) 101 | return &QueryTx{Query: q.clone(tx), Error: tx.Error} 102 | } 103 | 104 | type QueryTx struct { 105 | *Query 106 | Error error 107 | } 108 | 109 | func (q *QueryTx) Commit() error { 110 | return q.db.Commit().Error 111 | } 112 | 113 | func (q *QueryTx) Rollback() error { 114 | return q.db.Rollback().Error 115 | } 116 | 117 | func (q *QueryTx) SavePoint(name string) error { 118 | return q.db.SavePoint(name).Error 119 | } 120 | 121 | func (q *QueryTx) RollbackTo(name string) error { 122 | return q.db.RollbackTo(name).Error 123 | } 124 | -------------------------------------------------------------------------------- /internal/data/role.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/biz" 8 | "auth/internal/data/model" 9 | "auth/internal/data/query" 10 | "github.com/go-cinch/common/constant" 11 | "github.com/go-cinch/common/copierx" 12 | "github.com/go-cinch/common/log" 13 | "github.com/go-cinch/common/utils" 14 | "gorm.io/gen" 15 | ) 16 | 17 | type roleRepo struct { 18 | data *Data 19 | action biz.ActionRepo 20 | } 21 | 22 | func NewRoleRepo(data *Data, action biz.ActionRepo) biz.RoleRepo { 23 | return &roleRepo{ 24 | data: data, 25 | action: action, 26 | } 27 | } 28 | 29 | func (ro roleRepo) Create(ctx context.Context, item *biz.Role) (err error) { 30 | p := query.Use(ro.data.DB(ctx)).Role 31 | db := p.WithContext(ctx) 32 | ok := ro.WordExists(ctx, item.Word) 33 | if ok { 34 | err = biz.ErrDuplicateField(ctx, p.Word.ColumnName().String(), item.Word) 35 | return 36 | } 37 | var m model.Role 38 | copierx.Copy(&m, item) 39 | m.ID = ro.data.ID(ctx) 40 | if m.Action != "" { 41 | err = ro.action.CodeExists(ctx, m.Action) 42 | if err != nil { 43 | return 44 | } 45 | } 46 | err = db.Create(&m) 47 | return 48 | } 49 | 50 | func (ro roleRepo) Find(ctx context.Context, condition *biz.FindRole) (rp []biz.Role) { 51 | p := query.Use(ro.data.DB(ctx)).Role 52 | db := p.WithContext(ctx) 53 | rp = make([]biz.Role, 0) 54 | list := make([]model.Role, 0) 55 | conditions := make([]gen.Condition, 0, 2) 56 | if condition.Name != nil { 57 | conditions = append(conditions, p.Name.Like(strings.Join([]string{"%", *condition.Name, "%"}, ""))) 58 | } 59 | if condition.Word != nil { 60 | conditions = append(conditions, p.Word.Like(strings.Join([]string{"%", *condition.Word, "%"}, ""))) 61 | } 62 | condition.Page.Primary = p.ID.ColumnName().String() 63 | condition.Page. 64 | WithContext(ctx). 65 | Query( 66 | db. 67 | Order(p.ID.Desc()). 68 | Where(conditions...). 69 | UnderlyingDB(), 70 | ). 71 | Find(&list) 72 | copierx.Copy(&rp, list) 73 | for i, item := range rp { 74 | rp[i].Actions = make([]biz.Action, 0) 75 | arr := ro.action.FindByCode(ctx, item.Action) 76 | copierx.Copy(&rp[i].Actions, arr) 77 | } 78 | return 79 | } 80 | 81 | func (ro roleRepo) Update(ctx context.Context, item *biz.UpdateRole) (err error) { 82 | p := query.Use(ro.data.DB(ctx)).Role 83 | db := p.WithContext(ctx) 84 | m := db.GetByID(item.Id) 85 | if m.ID == constant.UI0 { 86 | err = biz.ErrRecordNotFound(ctx) 87 | return 88 | } 89 | change := make(map[string]interface{}) 90 | utils.CompareDiff(m, item, &change) 91 | if len(change) == 0 { 92 | err = biz.ErrDataNotChange(ctx) 93 | return 94 | } 95 | if a, ok1 := change[p.Action.ColumnName().String()]; ok1 { 96 | if v, ok2 := a.(string); ok2 { 97 | err = ro.action.CodeExists(ctx, v) 98 | if err != nil { 99 | return 100 | } 101 | } 102 | } 103 | if item.Word != nil && *item.Word != m.Word { 104 | ok := ro.WordExists(ctx, *item.Word) 105 | if ok { 106 | err = biz.ErrDuplicateField(ctx, p.Word.ColumnName().String(), *item.Word) 107 | return 108 | } 109 | } 110 | _, err = db. 111 | Where(p.ID.Eq(item.Id)). 112 | Updates(&change) 113 | return 114 | } 115 | 116 | func (ro roleRepo) Delete(ctx context.Context, ids ...uint64) (err error) { 117 | p := query.Use(ro.data.DB(ctx)).Role 118 | db := p.WithContext(ctx) 119 | _, err = db. 120 | Where(p.ID.In(ids...)). 121 | Delete() 122 | return 123 | } 124 | 125 | func (ro roleRepo) WordExists(ctx context.Context, word string) (ok bool) { 126 | p := query.Use(ro.data.DB(ctx)).Role 127 | db := p.WithContext(ctx) 128 | arr := strings.Split(word, ",") 129 | for _, item := range arr { 130 | m := db.GetByCol(p.Word.ColumnName().String(), item) 131 | if m.ID == constant.UI0 { 132 | log. 133 | WithContext(ctx). 134 | Error("invalid word: %s", item) 135 | return 136 | } 137 | } 138 | ok = true 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /internal/data/tracer.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "auth/internal/conf" 8 | "github.com/go-cinch/common/log" 9 | "github.com/pkg/errors" 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/attribute" 12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 13 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 14 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 15 | "go.opentelemetry.io/otel/sdk/resource" 16 | "go.opentelemetry.io/otel/sdk/trace" 17 | semconv "go.opentelemetry.io/otel/semconv/v1.26.0" 18 | "google.golang.org/grpc" 19 | ) 20 | 21 | func NewTracer(c *conf.Bootstrap) (tp *trace.TracerProvider, err error) { 22 | if !c.Tracer.Enable { 23 | log.Info("skip initialize tracer") 24 | return 25 | } 26 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 27 | defer cancel() 28 | var exporter trace.SpanExporter 29 | var resourcer *resource.Resource 30 | attrs := []attribute.KeyValue{semconv.ServiceNameKey.String(c.Name)} 31 | if c.Tracer.Otlp.Endpoint != "" { 32 | // rpc driver 33 | driverOpts := []otlptracegrpc.Option{ 34 | otlptracegrpc.WithEndpoint(c.Tracer.Otlp.Endpoint), 35 | otlptracegrpc.WithDialOption(grpc.WithBlock()), 36 | } 37 | if c.Tracer.Otlp.Insecure { 38 | driverOpts = append(driverOpts, otlptracegrpc.WithInsecure()) 39 | } 40 | driver := otlptracegrpc.NewClient(driverOpts...) 41 | exporter, err = otlptrace.New(ctx, driver) 42 | resourcer = resource.NewSchemaless(attrs...) 43 | } else { 44 | // stdout driver 45 | if c.Tracer.Stdout.PrettyPrint { 46 | exporter, err = stdouttrace.New(stdouttrace.WithPrettyPrint()) 47 | } else { 48 | exporter, err = stdouttrace.New() 49 | } 50 | resourcer = resource.NewSchemaless(attrs...) 51 | } 52 | 53 | if err != nil { 54 | log.Error(err) 55 | err = errors.New("initialize tracer failed") 56 | return 57 | } 58 | providerOpts := []trace.TracerProviderOption{ 59 | trace.WithBatcher(exporter), 60 | trace.WithResource(resourcer), 61 | trace.WithSampler(trace.TraceIDRatioBased(float64(c.Tracer.Ratio))), 62 | } 63 | tp = trace.NewTracerProvider(providerOpts...) 64 | otel.SetTracerProvider(tp) 65 | log.Info("initialize tracer success, ratio: %v", c.Tracer.Ratio) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/data/user_group.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/biz" 8 | "auth/internal/data/model" 9 | "auth/internal/data/query" 10 | "github.com/go-cinch/common/constant" 11 | "github.com/go-cinch/common/copierx" 12 | "github.com/go-cinch/common/log" 13 | "github.com/go-cinch/common/utils" 14 | "gorm.io/gen" 15 | ) 16 | 17 | type userGroupRepo struct { 18 | data *Data 19 | action biz.ActionRepo 20 | user biz.UserRepo 21 | } 22 | 23 | type UserUserGroupRelation struct { 24 | UserId uint64 `json:"userId,string"` 25 | UserGroupId uint64 `json:"userGroupId,string"` 26 | } 27 | 28 | func NewUserGroupRepo(data *Data, action biz.ActionRepo, user biz.UserRepo) biz.UserGroupRepo { 29 | return &userGroupRepo{ 30 | data: data, 31 | action: action, 32 | user: user, 33 | } 34 | } 35 | 36 | func (ro userGroupRepo) Create(ctx context.Context, item *biz.UserGroup) (err error) { 37 | p := query.Use(ro.data.DB(ctx)).UserGroup 38 | db := p.WithContext(ctx) 39 | m := db.GetByCol(p.Word.ColumnName().String(), item.Word) 40 | if m.ID > constant.UI0 { 41 | err = biz.ErrDuplicateField(ctx, p.Word.ColumnName().String(), item.Word) 42 | return 43 | } 44 | copierx.Copy(&m, item) 45 | m.ID = ro.data.ID(ctx) 46 | if m.Action != "" { 47 | err = ro.action.CodeExists(ctx, m.Action) 48 | if err != nil { 49 | return 50 | } 51 | } 52 | if len(item.Users) > 0 { 53 | m.Users = make([]model.User, 0) 54 | for _, v := range item.Users { 55 | err = ro.user.IdExists(ctx, v.Id) 56 | if err != nil { 57 | return 58 | } 59 | m.Users = append(m.Users, model.User{ 60 | ID: v.Id, 61 | }) 62 | } 63 | } 64 | err = db.Create(&m) 65 | return 66 | } 67 | 68 | func (ro userGroupRepo) Find(ctx context.Context, condition *biz.FindUserGroup) (rp []biz.UserGroup) { 69 | p := query.Use(ro.data.DB(ctx)).UserGroup 70 | db := p.WithContext(ctx) 71 | rp = make([]biz.UserGroup, 0) 72 | list := make([]model.UserGroup, 0) 73 | conditions := make([]gen.Condition, 0, 2) 74 | if condition.Name != nil { 75 | conditions = append(conditions, p.Name.Like(strings.Join([]string{"%", *condition.Name, "%"}, ""))) 76 | } 77 | if condition.Word != nil { 78 | conditions = append(conditions, p.Word.Like(strings.Join([]string{"%", *condition.Word, "%"}, ""))) 79 | } 80 | if condition.Action != nil { 81 | conditions = append(conditions, p.Action.Like(strings.Join([]string{"%", *condition.Action, "%"}, ""))) 82 | } 83 | condition.Page.Primary = p.ID.ColumnName().String() 84 | condition.Page. 85 | WithContext(ctx). 86 | Query( 87 | db. 88 | Preload(p.Users). 89 | Order(p.ID.Desc()). 90 | Where(conditions...). 91 | UnderlyingDB(). 92 | Model(&model.UserGroup{}), 93 | ). 94 | Find(&list) 95 | copierx.Copy(&rp, list) 96 | for i, item := range rp { 97 | rp[i].Actions = make([]biz.Action, 0) 98 | arr := ro.action.FindByCode(ctx, item.Action) 99 | copierx.Copy(&rp[i].Actions, arr) 100 | } 101 | return 102 | } 103 | 104 | func (ro userGroupRepo) Update(ctx context.Context, item *biz.UpdateUserGroup) (err error) { 105 | p := query.Use(ro.data.DB(ctx)).UserGroup 106 | db := p.WithContext(ctx) 107 | m := db.GetByID(item.Id) 108 | if m.ID == constant.UI0 { 109 | err = biz.ErrRecordNotFound(ctx) 110 | return 111 | } 112 | change := make(map[string]interface{}) 113 | utils.CompareDiff(m, item, &change) 114 | if len(change) == 0 { 115 | err = biz.ErrDataNotChange(ctx) 116 | return 117 | } 118 | if item.Word != nil && *item.Word != m.Word { 119 | ok := ro.WordExists(ctx, *item.Word) 120 | if ok { 121 | err = biz.ErrDuplicateField(ctx, p.Word.ColumnName().String(), *item.Word) 122 | return 123 | } 124 | } 125 | if a, ok1 := change["users"]; ok1 { 126 | if v, ok2 := a.(string); ok2 { 127 | arr := utils.Str2Uint64Arr(v) 128 | users := make([]*model.User, 0) 129 | for _, id := range arr { 130 | users = append(users, &model.User{ 131 | ID: id, 132 | }) 133 | } 134 | err = p.Users. 135 | Model(&m). 136 | Replace(users...) 137 | if err != nil { 138 | return 139 | } 140 | delete(change, "users") 141 | } 142 | } 143 | _, err = db. 144 | Where(p.ID.Eq(item.Id)). 145 | Updates(&change) 146 | return 147 | } 148 | 149 | func (ro userGroupRepo) Delete(ctx context.Context, ids ...uint64) (err error) { 150 | p := query.Use(ro.data.DB(ctx)).UserGroup 151 | db := p.WithContext(ctx) 152 | _, err = db. 153 | Where(p.ID.In(ids...)). 154 | Delete() 155 | return 156 | } 157 | 158 | func (ro userGroupRepo) WordExists(ctx context.Context, word string) (ok bool) { 159 | p := query.Use(ro.data.DB(ctx)).UserGroup 160 | db := p.WithContext(ctx) 161 | arr := strings.Split(word, ",") 162 | for _, item := range arr { 163 | m := db.GetByCol(p.Word.ColumnName().String(), item) 164 | if m.ID == constant.UI0 { 165 | log. 166 | WithContext(ctx). 167 | Error("invalid word: %s", item) 168 | return 169 | } 170 | } 171 | ok = true 172 | return 173 | } 174 | -------------------------------------------------------------------------------- /internal/data/whitelist.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "auth/internal/biz" 8 | "auth/internal/data/model" 9 | "auth/internal/data/query" 10 | "github.com/go-cinch/common/constant" 11 | "github.com/go-cinch/common/copierx" 12 | "github.com/go-cinch/common/utils" 13 | "gorm.io/gen" 14 | ) 15 | 16 | type whitelistRepo struct { 17 | data *Data 18 | action biz.ActionRepo 19 | hotspot biz.HotspotRepo 20 | } 21 | 22 | func NewWhitelistRepo(data *Data, action biz.ActionRepo, hotspot biz.HotspotRepo) biz.WhitelistRepo { 23 | return &whitelistRepo{ 24 | data: data, 25 | action: action, 26 | hotspot: hotspot, 27 | } 28 | } 29 | 30 | func (ro whitelistRepo) Create(ctx context.Context, item *biz.Whitelist) (err error) { 31 | var m model.Whitelist 32 | copierx.Copy(&m, item) 33 | p := query.Use(ro.data.DB(ctx)).Whitelist 34 | db := p.WithContext(ctx) 35 | m.ID = ro.data.ID(ctx) 36 | err = db.Create(&m) 37 | return 38 | } 39 | 40 | func (ro whitelistRepo) Find(ctx context.Context, condition *biz.FindWhitelist) (rp []biz.Whitelist) { 41 | p := query.Use(ro.data.DB(ctx)).Whitelist 42 | db := p.WithContext(ctx) 43 | rp = make([]biz.Whitelist, 0) 44 | list := make([]model.Whitelist, 0) 45 | conditions := make([]gen.Condition, 0, 2) 46 | if condition.Category != nil { 47 | conditions = append(conditions, p.Category.Eq(*condition.Category)) 48 | } 49 | if condition.Resource != nil { 50 | conditions = append(conditions, p.Resource.Like(strings.Join([]string{"%", *condition.Resource, "%"}, ""))) 51 | } 52 | condition.Page.Primary = p.ID.ColumnName().String() 53 | condition.Page. 54 | WithContext(ctx). 55 | Query( 56 | db. 57 | Order(p.ID.Desc()). 58 | Where(conditions...). 59 | UnderlyingDB(), 60 | ). 61 | Find(&list) 62 | copierx.Copy(&rp, list) 63 | return 64 | } 65 | 66 | func (ro whitelistRepo) Has(ctx context.Context, condition *biz.HasWhitelist) (has bool) { 67 | resources := ro.hotspot.FindWhitelistResourceByCategory(ctx, condition.Category) 68 | for _, item := range resources { 69 | has = ro.action.MatchResource(ctx, item, condition.Permission) 70 | if has { 71 | return 72 | } 73 | } 74 | return 75 | } 76 | 77 | func (ro whitelistRepo) Update(ctx context.Context, item *biz.UpdateWhitelist) (err error) { 78 | p := query.Use(ro.data.DB(ctx)).Whitelist 79 | db := p.WithContext(ctx) 80 | m := db.GetByID(item.Id) 81 | if m.ID == constant.UI0 { 82 | err = biz.ErrRecordNotFound(ctx) 83 | return 84 | } 85 | change := make(map[string]interface{}) 86 | utils.CompareDiff(m, item, &change) 87 | if len(change) == 0 { 88 | err = biz.ErrDataNotChange(ctx) 89 | return 90 | } 91 | _, err = db. 92 | Where(p.ID.Eq(item.Id)). 93 | Updates(&change) 94 | return 95 | } 96 | 97 | func (ro whitelistRepo) Delete(ctx context.Context, ids ...uint64) (err error) { 98 | p := query.Use(ro.data.DB(ctx)).Whitelist 99 | db := p.WithContext(ctx) 100 | _, err = db. 101 | Where(p.ID.In(ids...)). 102 | Delete() 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "embed" 4 | 5 | const SQLRoot = "migrations" 6 | 7 | //go:embed migrations/*.sql 8 | var SQLFiles embed.FS 9 | -------------------------------------------------------------------------------- /internal/db/migrations/2022092814-user.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | -- SQL in section 'Up' is executed when this migration is applied 3 | CREATE TABLE `user` 4 | ( 5 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 6 | `created_at` DATETIME(3) NULL COMMENT 'create time', 7 | `updated_at` DATETIME(3) NULL COMMENT 'update time', 8 | `role_id` BIGINT UNSIGNED NULL COMMENT 'role id', 9 | `action` LONGTEXT NULL COMMENT 'user action code array', 10 | `username` VARCHAR(191) NULL COMMENT 'user login name', 11 | `code` CHAR(8) NOT NULL COMMENT 'user code', 12 | `password` LONGTEXT NULL COMMENT 'password', 13 | `platform` VARCHAR(50) NULL COMMENT 'device platform: pc/android/ios/mini...', 14 | `last_login` DATETIME(3) NULL COMMENT 'last login time', 15 | `locked` TINYINT(1) DEFAULT 0 NULL COMMENT 'locked(0: unlock, 1: locked)', 16 | `lock_expire` BIGINT UNSIGNED NULL COMMENT 'lock expiration time', 17 | `wrong` BIGINT UNSIGNED NULL COMMENT 'type wrong password count' 18 | ) ENGINE = InnoDB 19 | DEFAULT CHARSET = utf8mb4 20 | COLLATE = utf8mb4_general_ci; 21 | 22 | CREATE UNIQUE INDEX `idx_username` ON `user` (`username`); 23 | CREATE UNIQUE INDEX `idx_code` ON `user` (`code`); 24 | 25 | -- +migrate Down 26 | DROP TABLE `user`; 27 | -------------------------------------------------------------------------------- /internal/db/migrations/2022101016-action.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | -- SQL in section 'Up' is executed when this migration is applied 3 | CREATE TABLE `action` 4 | ( 5 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 6 | `name` VARCHAR(50) NULL COMMENT 'name', 7 | `code` CHAR(8) NOT NULL COMMENT 'code', 8 | `word` VARCHAR(50) NULL COMMENT 'keyword, must be unique, used as frontend display', 9 | `resource` LONGTEXT NULL COMMENT 'resource array, split by break line str, example: GET|/user+\n+PUT,PATCH|/role/*+\n+GET|/action', 10 | `menu` LONGTEXT NULL COMMENT 'menu array, split by break line str', 11 | `btn` LONGTEXT NULL COMMENT 'btn array, split by break line str' 12 | ) ENGINE = InnoDB 13 | DEFAULT CHARSET = utf8mb4 14 | COLLATE = utf8mb4_general_ci; 15 | 16 | CREATE UNIQUE INDEX `idx_word` ON `action` (`word`); 17 | CREATE UNIQUE INDEX `idx_code` ON `action` (`code`); 18 | 19 | -- +migrate Down 20 | DROP TABLE `action`; 21 | -------------------------------------------------------------------------------- /internal/db/migrations/2022101017-role.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | -- SQL in section 'Up' is executed when this migration is applied 3 | CREATE TABLE `role` 4 | ( 5 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 6 | `name` VARCHAR(50) NULL COMMENT 'name', 7 | `word` VARCHAR(50) NULL COMMENT 'keyword, must be unique, used as frontend display', 8 | `action` LONGTEXT NULL COMMENT 'role action code array' 9 | ) ENGINE = InnoDB 10 | DEFAULT CHARSET = utf8mb4 11 | COLLATE = utf8mb4_general_ci; 12 | 13 | CREATE UNIQUE INDEX `idx_word` ON `role` (`word`); 14 | 15 | -- +migrate Down 16 | DROP TABLE `role`; 17 | -------------------------------------------------------------------------------- /internal/db/migrations/2022101114-user-group.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | -- SQL in section 'Up' is executed when this migration is applied 3 | CREATE TABLE `user_group` 4 | ( 5 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 6 | `name` VARCHAR(50) NULL COMMENT 'name', 7 | `word` VARCHAR(50) NULL COMMENT 'keyword, must be unique, used as frontend display', 8 | `action` LONGTEXT NULL COMMENT 'user group action code array' 9 | ) ENGINE = InnoDB 10 | DEFAULT CHARSET = utf8mb4 11 | COLLATE = utf8mb4_general_ci; 12 | 13 | CREATE UNIQUE INDEX `idx_word` ON `user_group` (`word`); 14 | 15 | CREATE TABLE `user_user_group_relation` 16 | ( 17 | `user_id` BIGINT UNSIGNED NOT NULL COMMENT 'auto increment id', 18 | `user_group_id` BIGINT UNSIGNED NOT NULL COMMENT 'auto increment id', 19 | PRIMARY KEY (`user_id`, `user_group_id`) 20 | ) ENGINE = InnoDB 21 | DEFAULT CHARSET = utf8mb4 22 | COLLATE = utf8mb4_general_ci; 23 | 24 | -- +migrate Down 25 | DROP TABLE `user_group`; 26 | DROP TABLE `user_user_group_relation`; 27 | -------------------------------------------------------------------------------- /internal/db/migrations/2023060210-whitelist.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | CREATE TABLE `whitelist` 3 | ( 4 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'auto increment id' PRIMARY KEY, 5 | `category` SMALLINT UNSIGNED NOT NULL COMMENT 'category(0:permission, 1:jwt)', 6 | `resource` LONGTEXT NOT NULL COMMENT 'resource array, split by break line str, example: GET|/user+\n+PUT,PATCH|/role/*+\n+GET|/action' 7 | ) ENGINE = InnoDB 8 | DEFAULT CHARSET = utf8mb4 9 | COLLATE = utf8mb4_general_ci; 10 | 11 | -- +migrate Down 12 | DROP TABLE `whitelist`; 13 | -------------------------------------------------------------------------------- /internal/db/migrations/2023060217-default-data.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | -- SQL in section 'Up' is executed when this migration is applied 3 | INSERT INTO `action` (`id`, `name`, `code`, `word`, `resource`, `menu`, `btn`) VALUES 4 | (8926844486248562689,'全部权限','SN2837AY','*','*','*','*'), 5 | (8926844486248579238,'默认权限','KHXK5JVL','default','POST|/auth/logout|/auth.v1.Auth/Logout\nGET|/auth/info|/auth.v1.Auth/Info\nPOST|/auth/pwd|/auth.v1.Auth/Pwd','/dashboard/base\n/user/index',''), 6 | (8929298412088590337,'首页','2QKHTYEE','dashboard','','/dashboard/base',''), 7 | (8929306305215070209,'用户查询','GRNA3NPV','user.read','GET|/auth/user/list|/auth.v1.Auth/FindUser','/system/user','system.user.read'), 8 | (8929306391416406017,'用户新增','2LV9MDWB','user.create','POST|/auth/register|/auth.v1.Auth/Register','/system/user','system.user.create'), 9 | (8929306434114420737,'用户修改','NME3CT5H','user.update','PUT,PATCH|/auth/user/update|/auth.v1.Auth/UpdateUser\nGET|/auth/action|/auth.v1.Auth/FindAction','/system/user','system.user.update'), 10 | (8929306468524490753,'用户删除','EQH37R9C','user.delete','DELETE|/auth/user/delete|/auth.v1.Auth/DeleteUser','/system/user','system.user.delete'), 11 | (8929306872301748225,'用户组查询','V2HRXGW9','user.group.read','GET|/auth/user/group/list|/auth.v1.Auth/FindUserGroup','/system/group','system.user.group.read'), 12 | (8929306977897545729,'用户组新增','GGKPXAL6','user.group.create','POST|/auth/user/group/create|/auth.v1.Auth/CreateUserGroup','/system/group','system.user.group.create'), 13 | (8929307003835121665,'用户组修改','JM3TT968','user.group.update','PUT,PATCH|/auth/user/group/update|/auth.v1.Auth/UpdateUserGroup\nGET|/auth/user/list|/auth.v1.Auth/FindUser\nGET|/auth/action/list|/auth.v1.Auth/FindAction','/system/group','system.user.group.update'), 14 | (8929307052153503745,'用户组删除','JE45TMPQ','user.group.delete','DELETE|/auth/user/group/delete|/auth.v1.Auth/DeleteUserGroup','/system/group','system.user.group.delete'), 15 | (8929716482426798081,'角色查询','AS2V9HND','role.read','GET|/auth/role/list|/auth.v1.Auth/FindRole','/system/role','system.role.read'), 16 | (8929716516635541505,'角色创建','88BA22VF','role.create','POST|/auth/role/create|/auth.v1.Auth/CreateRole','/system/role','system.role.create'), 17 | (8929716548663246849,'角色修改','GE5YBVDN','role.update','PUT,PATCH|/auth/role/update|/auth.v1.Auth/UpdateRole','/system/role','system.role.update'), 18 | (8929716593156423681,'角色删除','AY6QE7QG','role.delete','DELETE|/auth/role/delete|/auth.v1.Auth/DeleteRole','/system/role','system.role.delete'), 19 | (8929717994339172353,'行为查询','42TMWNP3','action.read','GET|/auth/action/list|/auth.v1.Auth/FindAction','/system/action','system.action.read'), 20 | (8929718040577179649,'行为新增','SXPYFM3K','action.create','POST|/auth/action/create|/auth.v1.Auth/CreateAction','/system/action','system.action.create'), 21 | (8929718085473009665,'行为修改','8VCXMSCW','action.update','PUT,PATCH|/auth/action/update|/auth.v1.Auth/UpdateAction','/system/action','system.action.update'), 22 | (8929718119446872065,'行为删除','86QSDSRL','action.delete','DELETE|/auth/action/delete|/auth.v1.Auth/DeleteAction','/system/action','system.action.delete'), 23 | (8946576676020551681,'白名单查询','ALX2LHB2','whitelist.read','GET|/auth/whitelist/list|/auth.v1.Auth/FindWhitelist','/system/whitelist','system.whitelist.read'), 24 | (8946576685583564801,'白名单新增','ALCARRQ8','whitelist.create','POST|/auth/whitelist/create|/auth.v1.Auth/CreateWhitelist','/system/whitelist','system.whitelist.create'), 25 | (8946576696086102017,'白名单修改','28FN73B3','whitelist.update','PUT,PATCH|/auth/whitelist/update|/auth.v1.Auth/UpdateWhitelist','/system/whitelist','system.whitelist.update'), 26 | (8946576704491487233,'白名单删除','E8SN4T9K','whitelist.delete','DELETE|/auth/whitelist/delete|/auth.v1.Auth/DeleteWhitelist','/system/whitelist','system.whitelist.delete'); 27 | 28 | INSERT INTO `role` (`id`, `name`, `word`, `action`) VALUES 29 | (8929718176338411521,'管理员','admin','SN2837AY'), 30 | (8929721534264639489,'访客','guest','2QKHTYEE'); 31 | 32 | INSERT INTO `user_group` (`id`, `name`, `word`, `action`) VALUES 33 | (8929306707314606081,'只读','readonly','GRNA3NPV,V2HRXGW9,AS2V9HND,42TMWNP3'), 34 | (8929306725685657601,'读写','write','GRNA3NPV,2LV9MDWB,NME3CT5H,EQH37R9C,V2HRXGW9,GGKPXAL6,JM3TT968,JE45TMPQ,AS2V9HND,88BA22VF,GE5YBVDN,AY6QE7QG,42TMWNP3,SXPYFM3K,8VCXMSCW,86QSDSRL'), 35 | (8929717758803836929,'不能删除','nodelete','GRNA3NPV,2LV9MDWB,NME3CT5H,V2HRXGW9,GGKPXAL6,JM3TT968,AS2V9HND,88BA22VF,GE5YBVDN,42TMWNP3,SXPYFM3K,8VCXMSCW'); 36 | 37 | INSERT INTO `user` (`id`, `created_at`, `updated_at`, `role_id`, `username`, `code`, `password`, `platform`) VALUES 38 | (8929281526625992705,NOW(),NOW(),8929718176338411521,'super','89HEK28Y','$2a$10$TRT9yIpxi3LLgBnVrvktDOpxYUeSpq4cKDhuSDU8n16iXRPWkvmxG','pc'), 39 | (8929298014988664833,NOW()+1,NOW()+1,8929721534264639489,'guest','4VPNKE6M','$2a$10$er8ILElzUu9m7n6DLWZaPeG8h6R2hyySGawvx4y7E/CXKYfvxKifW','pc'), 40 | (8929306627069181953,NOW()+2,NOW()+2,0,'readonly','EXP78RGH','$2a$10$a5pNKJGB3X1BScsEUkA6Yub184Q99SiNbxbftJsOG88liuIKlnxcW','pc'), 41 | (8929306650406289409,NOW()+3,NOW()+3,0,'write','6SHWH93V','$2a$10$C.9Zfx/D0n9tep8zXP4jUekz58ClC6Zrx.vMjwxHCNPB6Rblib//S','pc'), 42 | (8929717570412478465,NOW()+4,NOW()+4,0,'nodelete','JJHWJ9YJ','$2a$10$8SPpr/z.ukV4IvSVUIHVQOhKzY3Xfp9QJla5poW4/HgBeMxSviQ22','pc'); 43 | 44 | INSERT INTO `user_user_group_relation` (`user_id`, `user_group_id`) VALUES 45 | (8929306627069181953,8929306707314606081), 46 | (8929306650406289409,8929306725685657601), 47 | (8929717570412478465,8929717758803836929); 48 | 49 | INSERT INTO `whitelist` (`id`, `category`, `resource`) VALUES 50 | (8946576552976449537, 0, '/grpc.health.v1.Health/Check\n/grpc.health.v1.Health/Watch'), 51 | (8946576560039657473, 1, '/grpc.health.v1.Health/Check\n/grpc.health.v1.Health/Watch'); 52 | 53 | -- +migrate Down 54 | TRUNCATE TABLE `action`; 55 | TRUNCATE TABLE `role`; 56 | TRUNCATE TABLE `user_group`; 57 | TRUNCATE TABLE `user`; 58 | TRUNCATE TABLE `user_user_group_relation`; 59 | TRUNCATE TABLE `whitelist`; 60 | -------------------------------------------------------------------------------- /internal/pkg/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "auth/internal/biz" 9 | "auth/internal/conf" 10 | "github.com/go-cinch/common/log" 11 | "github.com/go-cinch/common/utils" 12 | "github.com/go-cinch/common/worker" 13 | "github.com/google/wire" 14 | "github.com/pkg/errors" 15 | "go.opentelemetry.io/otel" 16 | ) 17 | 18 | // ProviderSet is task providers. 19 | var ProviderSet = wire.NewSet(New) 20 | 21 | // New is initialize task worker from config 22 | func New(c *conf.Bootstrap, user *biz.UserUseCase, hotspot *biz.HotspotUseCase) (w *worker.Worker, err error) { 23 | w = worker.New( 24 | worker.WithRedisURI(c.Data.Redis.Dsn), 25 | worker.WithGroup(c.Name), 26 | worker.WithHandlerNeedWorker(func(ctx context.Context, w worker.Worker, p worker.Payload) error { 27 | return process(task{ 28 | ctx: ctx, 29 | c: c, 30 | w: w, 31 | payload: p, 32 | user: user, 33 | hotspot: hotspot, 34 | }) 35 | }), 36 | ) 37 | if w.Error != nil { 38 | log.Error(w.Error) 39 | err = errors.New("initialize worker failed") 40 | return 41 | } 42 | 43 | for id, item := range c.Task.Cron { 44 | err = w.Cron( 45 | context.Background(), 46 | worker.WithRunUUID(id), 47 | worker.WithRunGroup(item.Name), 48 | worker.WithRunExpr(item.Expr), 49 | worker.WithRunTimeout(int(item.Timeout)), 50 | worker.WithRunMaxRetry(int(item.Retry)), 51 | ) 52 | if err != nil { 53 | log.Error(err) 54 | err = errors.New("initialize worker failed") 55 | return 56 | } 57 | } 58 | 59 | log.Info("initialize worker success") 60 | // when app restart, clear hotspot 61 | _ = w.Once( 62 | context.Background(), 63 | worker.WithRunUUID(strings.Join([]string{c.Task.Group.RefreshHotspotManual}, ".")), 64 | worker.WithRunGroup(c.Task.Group.RefreshHotspotManual), 65 | worker.WithRunIn(10*time.Second), 66 | worker.WithRunReplace(true), 67 | ) 68 | return 69 | } 70 | 71 | type task struct { 72 | ctx context.Context 73 | c *conf.Bootstrap 74 | w worker.Worker 75 | payload worker.Payload 76 | user *biz.UserUseCase 77 | hotspot *biz.HotspotUseCase 78 | } 79 | 80 | func process(t task) (err error) { 81 | tr := otel.Tracer("task") 82 | ctx, span := tr.Start(t.ctx, "Task") 83 | defer span.End() 84 | switch t.payload.Group { 85 | case t.c.Task.Group.LoginFailed: 86 | var req biz.LoginTime 87 | utils.JSON2Struct(&req, t.payload.Payload) 88 | err = t.user.WrongPwd(ctx, &req) 89 | case t.c.Task.Group.LoginLast: 90 | var req biz.LoginTime 91 | utils.JSON2Struct(&req, t.payload.Payload) 92 | err = t.user.LastLogin(ctx, req.Username) 93 | case t.c.Task.Group.RefreshHotspot: 94 | err = t.hotspot.Refresh(ctx) 95 | case t.c.Task.Group.RefreshHotspotManual: 96 | err = t.hotspot.Refresh(ctx) 97 | } 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /internal/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "auth/api/auth" 5 | "auth/internal/biz" 6 | "auth/internal/conf" 7 | localMiddleware "auth/internal/server/middleware" 8 | "auth/internal/service" 9 | "github.com/go-cinch/common/i18n" 10 | i18nMiddleware "github.com/go-cinch/common/middleware/i18n" 11 | "github.com/go-cinch/common/middleware/logging" 12 | tenantMiddleware "github.com/go-cinch/common/middleware/tenant" 13 | traceMiddleware "github.com/go-cinch/common/middleware/trace" 14 | "github.com/go-kratos/kratos/v2/middleware" 15 | "github.com/go-kratos/kratos/v2/middleware/metadata" 16 | "github.com/go-kratos/kratos/v2/middleware/ratelimit" 17 | "github.com/go-kratos/kratos/v2/middleware/recovery" 18 | "github.com/go-kratos/kratos/v2/middleware/tracing" 19 | "github.com/go-kratos/kratos/v2/middleware/validate" 20 | "github.com/go-kratos/kratos/v2/transport/grpc" 21 | "github.com/redis/go-redis/v9" 22 | "golang.org/x/text/language" 23 | ) 24 | 25 | // NewGRPCServer new a gRPC server. 26 | func NewGRPCServer( 27 | c *conf.Bootstrap, 28 | svc *service.AuthService, 29 | rds redis.UniversalClient, 30 | whitelist *biz.WhitelistUseCase, 31 | ) *grpc.Server { 32 | var middlewares []middleware.Middleware 33 | if c.Tracer.Enable { 34 | middlewares = append(middlewares, tracing.Server(), traceMiddleware.ID()) 35 | } 36 | middlewares = append( 37 | middlewares, 38 | recovery.Recovery(), 39 | tenantMiddleware.Tenant(), 40 | ratelimit.Server(), 41 | localMiddleware.Header(), 42 | logging.Server(), 43 | i18nMiddleware.Translator(i18n.WithLanguage(language.Make(c.Server.Language)), i18n.WithFs(locales)), 44 | metadata.Server(), 45 | ) 46 | if c.Server.Jwt.Enable { 47 | middlewares = append(middlewares, localMiddleware.Permission(c, rds, whitelist)) 48 | } 49 | if c.Server.Idempotent { 50 | middlewares = append(middlewares, localMiddleware.Idempotent(rds)) 51 | } 52 | if c.Server.Validate { 53 | middlewares = append(middlewares, validate.Validator()) 54 | } 55 | var opts = []grpc.ServerOption{grpc.Middleware(middlewares...)} 56 | if c.Server.Grpc.Network != "" { 57 | opts = append(opts, grpc.Network(c.Server.Grpc.Network)) 58 | } 59 | if c.Server.Grpc.Addr != "" { 60 | opts = append(opts, grpc.Address(c.Server.Grpc.Addr)) 61 | } 62 | if c.Server.Grpc.Timeout != nil { 63 | opts = append(opts, grpc.Timeout(c.Server.Grpc.Timeout.AsDuration())) 64 | } 65 | srv := grpc.NewServer(opts...) 66 | auth.RegisterAuthServer(srv, svc) 67 | return srv 68 | } 69 | -------------------------------------------------------------------------------- /internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "auth/internal/service" 7 | ) 8 | 9 | func HealthHandler(svc *service.AuthService) *http.ServeMux { 10 | mux := http.NewServeMux() 11 | mux.HandleFunc("/pub/healthcheck", svc.HealthCheck) 12 | return mux 13 | } 14 | -------------------------------------------------------------------------------- /internal/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "auth/api/auth" 5 | "auth/internal/biz" 6 | "auth/internal/conf" 7 | localMiddleware "auth/internal/server/middleware" 8 | "auth/internal/service" 9 | "github.com/go-cinch/common/i18n" 10 | i18nMiddleware "github.com/go-cinch/common/middleware/i18n" 11 | "github.com/go-cinch/common/middleware/logging" 12 | tenantMiddleware "github.com/go-cinch/common/middleware/tenant" 13 | traceMiddleware "github.com/go-cinch/common/middleware/trace" 14 | "github.com/go-kratos/kratos/v2/middleware" 15 | "github.com/go-kratos/kratos/v2/middleware/metadata" 16 | "github.com/go-kratos/kratos/v2/middleware/ratelimit" 17 | "github.com/go-kratos/kratos/v2/middleware/recovery" 18 | "github.com/go-kratos/kratos/v2/middleware/tracing" 19 | "github.com/go-kratos/kratos/v2/middleware/validate" 20 | "github.com/go-kratos/kratos/v2/transport/http" 21 | "github.com/go-kratos/kratos/v2/transport/http/pprof" 22 | "github.com/redis/go-redis/v9" 23 | "golang.org/x/text/language" 24 | ) 25 | 26 | // NewHTTPServer new a HTTP server. 27 | func NewHTTPServer( 28 | c *conf.Bootstrap, 29 | svc *service.AuthService, 30 | rds redis.UniversalClient, 31 | whitelist *biz.WhitelistUseCase, 32 | ) *http.Server { 33 | var middlewares []middleware.Middleware 34 | if c.Tracer.Enable { 35 | middlewares = append(middlewares, tracing.Server(), traceMiddleware.ID()) 36 | } 37 | middlewares = append( 38 | middlewares, 39 | recovery.Recovery(), 40 | tenantMiddleware.Tenant(), 41 | ratelimit.Server(), 42 | localMiddleware.Header(), 43 | logging.Server(), 44 | i18nMiddleware.Translator(i18n.WithLanguage(language.Make(c.Server.Language)), i18n.WithFs(locales)), 45 | metadata.Server(), 46 | ) 47 | if c.Server.Jwt.Enable { 48 | middlewares = append(middlewares, localMiddleware.Permission(c, rds, whitelist)) 49 | } 50 | if c.Server.Idempotent { 51 | middlewares = append(middlewares, localMiddleware.Idempotent(rds)) 52 | } 53 | if c.Server.Validate { 54 | middlewares = append(middlewares, validate.Validator()) 55 | } 56 | var opts = []http.ServerOption{ 57 | // already set cors header in nginx 58 | // http.Filter(handlers.CORS( 59 | // handlers.AllowedHeaders([]string{"Content-Type", "Authorization", "X-Idempotent"}), 60 | // handlers.AllowedMethods([]string{"OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE"}), 61 | // handlers.AllowedOrigins([]string{"*"}), 62 | // handlers.AllowCredentials(), 63 | // )), 64 | http.Middleware(middlewares...), 65 | } 66 | if c.Server.Http.Network != "" { 67 | opts = append(opts, http.Network(c.Server.Http.Network)) 68 | } 69 | if c.Server.Http.Addr != "" { 70 | opts = append(opts, http.Address(c.Server.Http.Addr)) 71 | } 72 | if c.Server.Http.Timeout != nil { 73 | opts = append(opts, http.Timeout(c.Server.Http.Timeout.AsDuration())) 74 | } 75 | srv := http.NewServer(opts...) 76 | auth.RegisterAuthHTTPServer(srv, svc) 77 | srv.HandlePrefix("/debug/pprof", pprof.NewHandler()) 78 | srv.HandlePrefix("/pub/healthcheck", HealthHandler(svc)) 79 | return srv 80 | } 81 | -------------------------------------------------------------------------------- /internal/server/middleware/header.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2/middleware" 7 | "github.com/go-kratos/kratos/v2/transport" 8 | ) 9 | 10 | func Header() middleware.Middleware { 11 | return func(handler middleware.Handler) middleware.Handler { 12 | return func(ctx context.Context, req interface{}) (rp interface{}, err error) { 13 | tr, _ := transport.FromServerContext(ctx) 14 | switch tr.Kind() { 15 | case transport.KindHTTP: 16 | if tr.ReplyHeader() != nil { 17 | tr.ReplyHeader().Set("x-content-type-options", "nosniff") 18 | tr.ReplyHeader().Set("x-xss-protection", "1; mode=block") 19 | tr.ReplyHeader().Set("x-frame-options", "deny") 20 | tr.ReplyHeader().Set("cache-control", "private") 21 | } 22 | } 23 | return handler(ctx, req) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/server/middleware/idempotent.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/internal/biz" 7 | "github.com/go-cinch/common/idempotent" 8 | "github.com/go-kratos/kratos/v2/middleware" 9 | "github.com/go-kratos/kratos/v2/transport" 10 | "github.com/redis/go-redis/v9" 11 | ) 12 | 13 | func Idempotent(rds redis.UniversalClient) middleware.Middleware { 14 | idt := idempotent.New( 15 | idempotent.WithPrefix("idempotent"), 16 | idempotent.WithRedis(rds), 17 | ) 18 | return func(handler middleware.Handler) middleware.Handler { 19 | return func(ctx context.Context, req interface{}) (rp interface{}, err error) { 20 | tr, _ := transport.FromServerContext(ctx) 21 | // check idempotent token if it has header 22 | token := tr.RequestHeader().Get("x-idempotent") 23 | if token == "" { 24 | return handler(ctx, req) 25 | } 26 | pass := idt.Check(ctx, token) 27 | if !pass { 28 | err = biz.ErrIdempotentTokenExpired(ctx) 29 | return 30 | } 31 | return handler(ctx, req) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/server/middleware/locales/en.yml: -------------------------------------------------------------------------------- 1 | jwt.token.missing: 'token is missing' 2 | jwt.token.invalid: 'token is invalid' 3 | jwt.token.expired: 'token has expired' 4 | jwt.token.parse.failed: 'fail to parse token' 5 | jwt.wrong.signing.method: 'wrong signing method' 6 | idempotent.token.invalid: 'idempotent token is invalid' 7 | 8 | too.many.requests: 'too many requests, please try again later' 9 | data.not.change: 'data has not changed' 10 | duplicate.field: 'duplicate field' 11 | record.not.found: 'record not found' 12 | no.permission: 'no permission to access this resource' 13 | 14 | login.incorrect.password: 'incorrect password' 15 | login.same.password: 'same password' 16 | login.invalid.captcha: 'invalid captcha' 17 | login.failed: 'incorrect username or password' 18 | login.user.locked: 'user is locked' 19 | action.keep.least.one.action: 'keep at least one action' 20 | user.delete.yourself: 'you cannot delete yourself' 21 | -------------------------------------------------------------------------------- /internal/server/middleware/locales/zh.yml: -------------------------------------------------------------------------------- 1 | jwt.token.missing: '缺少jwt签名' 2 | jwt.token.invalid: 'jwt签名无效' 3 | jwt.token.expired: 'jwt签名过期' 4 | jwt.token.parse.failed: '无法解析jwt签名' 5 | jwt.wrong.signing.method: 'jwt签名算法不匹配' 6 | idempotent.token.invalid: '幂等性签名无效' 7 | 8 | too.many.requests: '请求频繁, 请稍候重试' 9 | data.not.change: '数据没有发生变化' 10 | duplicate.field: '字段重复' 11 | record.not.found: '记录不存在' 12 | no.permission: '无权访问' 13 | 14 | login.incorrect.password: '密码错误' 15 | login.same.password: '密码一致' 16 | login.invalid.captcha: '验证码过期' 17 | login.failed: '用户名或密码错误' 18 | login.user.locked: '用户已锁定' 19 | action.keep.least.one.action: '至少保留一个行为' 20 | user.delete.yourself: '禁止删除自己' 21 | -------------------------------------------------------------------------------- /internal/server/middleware/permission.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "auth/api/auth" 11 | "auth/internal/biz" 12 | "auth/internal/conf" 13 | "github.com/go-cinch/common/copierx" 14 | jwtLocal "github.com/go-cinch/common/jwt" 15 | "github.com/go-cinch/common/log" 16 | "github.com/go-cinch/common/utils" 17 | "github.com/go-kratos/kratos/v2/middleware" 18 | "github.com/go-kratos/kratos/v2/transport" 19 | kratosHttp "github.com/go-kratos/kratos/v2/transport/http" 20 | jwtV4 "github.com/golang-jwt/jwt/v4" 21 | "github.com/redis/go-redis/v9" 22 | "google.golang.org/protobuf/types/known/emptypb" 23 | ) 24 | 25 | const ( 26 | pubURIPrefix = "/pub/" 27 | jwtTokenCachePrefix = "jwt.token" 28 | jwtTokenCacheExpire = 10 * time.Minute 29 | permissionHeaderMethod = "x-original-method" 30 | permissionHeaderURI = "x-permission-uri" 31 | ) 32 | 33 | func Permission(c *conf.Bootstrap, client redis.UniversalClient, whitelist *biz.WhitelistUseCase) func(handler middleware.Handler) middleware.Handler { 34 | return func(handler middleware.Handler) middleware.Handler { 35 | return func(ctx context.Context, req interface{}) (rp interface{}, err error) { 36 | tr, _ := transport.FromServerContext(ctx) 37 | if tr.Kind() == transport.KindGRPC { 38 | // 1. grpc api for internal no need check 39 | // tip: u can add ur logic if need check grpc 40 | return handler(ctx, req) 41 | } 42 | // check http api 43 | var uri string 44 | operation := tr.Operation() 45 | if ht, ok := tr.(kratosHttp.Transporter); ok { 46 | uri = ht.Request().URL.Path 47 | } 48 | // 2. public api no need check 49 | if strings.Contains(uri, pubURIPrefix) { 50 | return handler(ctx, req) 51 | } 52 | if operation == auth.OperationAuthPermission && permissionWhitelist(ctx, whitelist, req) { 53 | // for nginx auth_request 54 | // 3. permission whitelist api no need check 55 | rp = &emptypb.Empty{} 56 | return 57 | } else if jwtWhitelist(ctx, whitelist) { 58 | // 4. jwt whitelist api no need check 59 | return handler(ctx, req) 60 | } 61 | user, err := parseJwt(ctx, c, client, c.Server.Jwt.Key) 62 | if err != nil { 63 | return 64 | } 65 | // pass user info into ctx 66 | ctx = jwtLocal.NewServerContextByUser(ctx, *user) 67 | return handler(ctx, req) 68 | } 69 | } 70 | } 71 | 72 | func permissionWhitelist(ctx context.Context, whitelist *biz.WhitelistUseCase, req interface{}) (ok bool) { 73 | tr, _ := transport.FromServerContext(ctx) 74 | var r biz.CheckPermission 75 | copierx.Copy(&r, req) 76 | // get from header if exist 77 | method := tr.RequestHeader().Get(permissionHeaderMethod) 78 | if method != "" { 79 | r.Method = method 80 | } 81 | uri := tr.RequestHeader().Get(permissionHeaderURI) 82 | if uri != "" { 83 | r.URI = uri 84 | } 85 | // public api no need check 86 | if strings.Contains(r.URI, pubURIPrefix) { 87 | return true 88 | } 89 | log. 90 | WithContext(ctx). 91 | Info("method: %s, uri: %s, resource: %s", r.Method, r.URI, r.Resource) 92 | // skip options 93 | if r.Method == http.MethodOptions || r.Method == http.MethodHead { 94 | return 95 | } 96 | // check if it is on the whitelist 97 | ok = whitelist.Has(ctx, &biz.HasWhitelist{ 98 | Category: biz.WhitelistPermissionCategory, 99 | Permission: &r, 100 | }) 101 | // override params 102 | v, ok2 := req.(*auth.PermissionRequest) 103 | if ok2 { 104 | v.Method = &r.Method 105 | v.Uri = &r.URI 106 | req = v 107 | return 108 | } 109 | return 110 | } 111 | 112 | func jwtWhitelist(ctx context.Context, whitelist *biz.WhitelistUseCase) bool { 113 | tr, _ := transport.FromServerContext(ctx) 114 | return whitelist.Has(ctx, &biz.HasWhitelist{ 115 | Category: biz.WhitelistJwtCategory, 116 | Permission: &biz.CheckPermission{ 117 | Resource: tr.Operation(), 118 | }, 119 | }) 120 | } 121 | 122 | func parseJwt(ctx context.Context, c *conf.Bootstrap, client redis.UniversalClient, jwtKey string) (user *jwtLocal.User, err error) { 123 | user = jwtLocal.FromServerContext(ctx) 124 | if user.Token == "" { 125 | err = biz.ErrJwtMissingToken(ctx) 126 | return 127 | } 128 | key := strings.Join([]string{c.Name, jwtTokenCachePrefix, utils.StructMd5(user.Token)}, ".") 129 | res, _ := client.Get(ctx, key).Result() 130 | if res != "" { 131 | utils.JSON2Struct(user, res) 132 | return 133 | } 134 | 135 | // parse Authorization jwt token to get user info 136 | var info *jwtV4.Token 137 | info, err = parseToken(ctx, jwtKey, user.Token) 138 | if err != nil { 139 | return 140 | } 141 | ctx = jwtLocal.NewServerContext(ctx, info.Claims, "code", "platform") 142 | user = jwtLocal.FromServerContext(ctx) 143 | client.Set(ctx, key, utils.Struct2JSON(user), jwtTokenCacheExpire) 144 | return 145 | } 146 | 147 | func parseToken(ctx context.Context, key, jwtToken string) (info *jwtV4.Token, err error) { 148 | info, err = jwtV4.Parse(jwtToken, func(_ *jwtV4.Token) (rp interface{}, err error) { 149 | rp = []byte(key) 150 | return 151 | }) 152 | if err != nil { 153 | var ve *jwtV4.ValidationError 154 | ok := errors.As(err, &ve) 155 | if !ok { 156 | return 157 | } 158 | if ve.Errors&jwtV4.ValidationErrorMalformed != 0 { 159 | err = biz.ErrJwtTokenInvalid(ctx) 160 | return 161 | } 162 | if ve.Errors&(jwtV4.ValidationErrorExpired|jwtV4.ValidationErrorNotValidYet) != 0 { 163 | err = biz.ErrJwtTokenExpired(ctx) 164 | return 165 | } 166 | err = biz.ErrJwtTokenParseFail(ctx) 167 | return 168 | } 169 | if !info.Valid { 170 | err = biz.ErrJwtTokenParseFail(ctx) 171 | return 172 | } 173 | if info.Method != jwtV4.SigningMethodHS512 { 174 | err = biz.ErrJwtUnSupportSigningMethod(ctx) 175 | return 176 | } 177 | return 178 | } 179 | -------------------------------------------------------------------------------- /internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/google/wire" 7 | ) 8 | 9 | // ProviderSet is server providers. 10 | var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer) 11 | 12 | //go:embed middleware/locales 13 | var locales embed.FS 14 | -------------------------------------------------------------------------------- /internal/service/README.md: -------------------------------------------------------------------------------- 1 | # Service 2 | -------------------------------------------------------------------------------- /internal/service/action.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/auth" 7 | "auth/internal/biz" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/page" 10 | "github.com/go-cinch/common/proto/params" 11 | "github.com/go-cinch/common/utils" 12 | "go.opentelemetry.io/otel" 13 | "google.golang.org/protobuf/types/known/emptypb" 14 | ) 15 | 16 | func (s *AuthService) CreateAction(ctx context.Context, req *auth.CreateActionRequest) (rp *emptypb.Empty, err error) { 17 | tr := otel.Tracer("api") 18 | ctx, span := tr.Start(ctx, "CreateAction") 19 | defer span.End() 20 | rp = &emptypb.Empty{} 21 | r := &biz.Action{} 22 | copierx.Copy(&r, req) 23 | err = s.action.Create(ctx, r) 24 | s.flushCache(ctx) 25 | return 26 | } 27 | 28 | func (s *AuthService) FindAction(ctx context.Context, req *auth.FindActionRequest) (rp *auth.FindActionReply, err error) { 29 | tr := otel.Tracer("api") 30 | ctx, span := tr.Start(ctx, "FindAction") 31 | defer span.End() 32 | rp = &auth.FindActionReply{} 33 | rp.Page = ¶ms.Page{} 34 | r := &biz.FindAction{} 35 | r.Page = page.Page{} 36 | copierx.Copy(&r, req) 37 | copierx.Copy(&r.Page, req.Page) 38 | res, err := s.action.Find(ctx, r) 39 | if err != nil { 40 | return 41 | } 42 | copierx.Copy(&rp.Page, r.Page) 43 | copierx.Copy(&rp.List, res) 44 | return 45 | } 46 | 47 | func (s *AuthService) UpdateAction(ctx context.Context, req *auth.UpdateActionRequest) (rp *emptypb.Empty, err error) { 48 | tr := otel.Tracer("api") 49 | ctx, span := tr.Start(ctx, "UpdateAction") 50 | defer span.End() 51 | rp = &emptypb.Empty{} 52 | r := &biz.UpdateAction{} 53 | copierx.Copy(&r, req) 54 | err = s.action.Update(ctx, r) 55 | s.flushCache(ctx) 56 | return 57 | } 58 | 59 | func (s *AuthService) DeleteAction(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 60 | tr := otel.Tracer("api") 61 | ctx, span := tr.Start(ctx, "DeleteAction") 62 | defer span.End() 63 | rp = &emptypb.Empty{} 64 | err = s.action.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 65 | s.flushCache(ctx) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "auth/api/auth" 9 | "auth/internal/biz" 10 | "github.com/go-cinch/common/copierx" 11 | "github.com/go-cinch/common/jwt" 12 | "github.com/go-cinch/common/utils" 13 | "github.com/go-cinch/common/worker" 14 | "github.com/golang-module/carbon/v2" 15 | "go.opentelemetry.io/otel" 16 | "google.golang.org/protobuf/types/known/emptypb" 17 | ) 18 | 19 | func (s *AuthService) Register(ctx context.Context, req *auth.RegisterRequest) (rp *emptypb.Empty, err error) { 20 | tr := otel.Tracer("api") 21 | ctx, span := tr.Start(ctx, "Register") 22 | defer span.End() 23 | rp = &emptypb.Empty{} 24 | r := &biz.User{} 25 | copierx.Copy(&r, req) 26 | if !s.user.VerifyCaptcha(ctx, req.CaptchaId, req.CaptchaAnswer) { 27 | err = biz.ErrInvalidCaptcha(ctx) 28 | return 29 | } 30 | err = s.user.Create(ctx, r) 31 | s.flushCache(ctx) 32 | return 33 | } 34 | 35 | func (s *AuthService) Pwd(ctx context.Context, req *auth.PwdRequest) (rp *emptypb.Empty, err error) { 36 | tr := otel.Tracer("api") 37 | ctx, span := tr.Start(ctx, "Pwd") 38 | defer span.End() 39 | rp = &emptypb.Empty{} 40 | r := &biz.User{} 41 | copierx.Copy(&r, req) 42 | err = s.user.Pwd(ctx, r) 43 | s.flushCache(ctx) 44 | return 45 | } 46 | 47 | func (s *AuthService) Login(ctx context.Context, req *auth.LoginRequest) (rp *auth.LoginReply, err error) { 48 | tr := otel.Tracer("api") 49 | ctx, span := tr.Start(ctx, "Login") 50 | defer span.End() 51 | rp = &auth.LoginReply{} 52 | r := &biz.Login{} 53 | copierx.Copy(&r, req) 54 | res, err := s.user.Login(ctx, r) 55 | if err != nil { 56 | loginFailedErr := biz.ErrLoginFailed(ctx) 57 | loginFailed := err.Error() == loginFailedErr.Error() 58 | notFound := err.Error() == biz.ErrRecordNotFound(ctx).Error() 59 | invalidCaptcha := err.Error() == biz.ErrInvalidCaptcha(ctx).Error() 60 | if invalidCaptcha { 61 | return 62 | } 63 | if notFound { 64 | // avoid guess username 65 | err = loginFailedErr 66 | return 67 | } 68 | if loginFailed { 69 | _ = s.task.Once( 70 | ctx, 71 | worker.WithRunUUID(strings.Join([]string{s.c.Task.Group.LoginFailed, req.Username}, ".")), 72 | worker.WithRunGroup(s.c.Task.Group.LoginFailed), 73 | worker.WithRunNow(true), 74 | worker.WithRunTimeout(10), 75 | worker.WithRunReplace(true), 76 | worker.WithRunPayload(utils.Struct2JSON(biz.LoginTime{ 77 | Username: req.Username, 78 | LastLogin: carbon.DateTime{ 79 | Carbon: carbon.Now(), 80 | }, 81 | Wrong: res.Wrong, 82 | })), 83 | ) 84 | // need refresh hotspot 85 | s.flushCache(ctx) 86 | return 87 | } 88 | return 89 | } 90 | copierx.Copy(&rp, res) 91 | _ = s.task.Once( 92 | ctx, 93 | worker.WithRunUUID(strings.Join([]string{s.c.Task.Group.LoginLast, req.Username}, ".")), 94 | worker.WithRunGroup(s.c.Task.Group.LoginLast), 95 | worker.WithRunIn(time.Duration(10)*time.Second), 96 | worker.WithRunTimeout(3), 97 | worker.WithRunReplace(true), 98 | worker.WithRunPayload(utils.Struct2JSON(biz.LoginTime{ 99 | Username: req.Username, 100 | })), 101 | ) 102 | s.flushCache(ctx) 103 | return 104 | } 105 | 106 | func (*AuthService) Logout(ctx context.Context, _ *emptypb.Empty) (rp *emptypb.Empty, err error) { 107 | tr := otel.Tracer("api") 108 | ctx, span := tr.Start(ctx, "Logout") 109 | defer span.End() 110 | rp = &emptypb.Empty{} 111 | return 112 | } 113 | 114 | func (s *AuthService) Status(ctx context.Context, req *auth.StatusRequest) (rp *auth.StatusReply, err error) { 115 | tr := otel.Tracer("api") 116 | ctx, span := tr.Start(ctx, "Status") 117 | defer span.End() 118 | rp = &auth.StatusReply{} 119 | res, err := s.user.Status(ctx, req.Username, true) 120 | if err != nil { 121 | return 122 | } 123 | copierx.Copy(&rp, res) 124 | return 125 | } 126 | 127 | func (s *AuthService) Captcha(ctx context.Context, _ *emptypb.Empty) (rp *auth.CaptchaReply, err error) { 128 | tr := otel.Tracer("api") 129 | ctx, span := tr.Start(ctx, "Captcha") 130 | defer span.End() 131 | rp = &auth.CaptchaReply{} 132 | rp.Captcha = &auth.Captcha{} 133 | res := s.user.Captcha(ctx) 134 | copierx.Copy(&rp.Captcha, res) 135 | return 136 | } 137 | 138 | func (s *AuthService) Permission(ctx context.Context, req *auth.PermissionRequest) (rp *emptypb.Empty, err error) { 139 | tr := otel.Tracer("api") 140 | ctx, span := tr.Start(ctx, "Permission") 141 | defer span.End() 142 | rp = &emptypb.Empty{} 143 | user := jwt.FromServerContext(ctx) 144 | r := &biz.CheckPermission{ 145 | UserCode: user.Attrs["code"], 146 | } 147 | if req.Resource != nil { 148 | r.Resource = *req.Resource 149 | } 150 | if req.Method != nil { 151 | r.Method = *req.Method 152 | } 153 | if req.Uri != nil { 154 | r.URI = *req.Uri 155 | } 156 | pass := s.permission.Check(ctx, r) 157 | if !pass { 158 | err = biz.ErrNoPermission(ctx) 159 | return 160 | } 161 | info := s.user.Info(ctx, user.Attrs["code"]) 162 | jwt.AppendToReplyHeader(ctx, jwt.User{ 163 | Attrs: map[string]string{ 164 | "code": info.Code, 165 | "platform": info.Platform, 166 | }, 167 | }) 168 | return 169 | } 170 | 171 | func (s *AuthService) Info(ctx context.Context, _ *emptypb.Empty) (rp *auth.InfoReply, err error) { 172 | tr := otel.Tracer("api") 173 | ctx, span := tr.Start(ctx, "Info") 174 | defer span.End() 175 | rp = &auth.InfoReply{} 176 | rp.Permission = &auth.Permission{} 177 | user := jwt.FromServerContext(ctx) 178 | res := s.user.Info(ctx, user.Attrs["code"]) 179 | permission := s.permission.GetByUserCode(ctx, user.Attrs["code"]) 180 | copierx.Copy(&rp.Permission, permission) 181 | copierx.Copy(&rp, res) 182 | return 183 | } 184 | -------------------------------------------------------------------------------- /internal/service/health.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-cinch/common/log" 7 | ) 8 | 9 | func (*AuthService) HealthCheck(writer http.ResponseWriter, _ *http.Request) { 10 | log.Debug("healthcheck") 11 | writer.Header().Set("Content-Type", "application/json") 12 | writer.WriteHeader(http.StatusOK) 13 | _, _ = writer.Write([]byte("{}")) 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /internal/service/hotspot.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/go-cinch/common/worker" 8 | ) 9 | 10 | func (s *AuthService) flushCache(ctx context.Context) { 11 | // clear user info cache 12 | s.user.FlushCache(ctx) 13 | 14 | // delay clear hotspot cache 15 | _ = s.task.Once( 16 | ctx, 17 | worker.WithRunUUID(strings.Join([]string{s.c.Task.Group.RefreshHotspotManual}, ".")), 18 | worker.WithRunGroup(s.c.Task.Group.RefreshHotspotManual), 19 | worker.WithRunNow(true), 20 | worker.WithRunReplace(true), 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /internal/service/role.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/auth" 7 | "auth/internal/biz" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/page" 10 | "github.com/go-cinch/common/proto/params" 11 | "github.com/go-cinch/common/utils" 12 | "go.opentelemetry.io/otel" 13 | "google.golang.org/protobuf/types/known/emptypb" 14 | ) 15 | 16 | func (s *AuthService) CreateRole(ctx context.Context, req *auth.CreateRoleRequest) (rp *emptypb.Empty, err error) { 17 | tr := otel.Tracer("api") 18 | ctx, span := tr.Start(ctx, "CreateRole") 19 | defer span.End() 20 | rp = &emptypb.Empty{} 21 | r := &biz.Role{} 22 | copierx.Copy(&r, req) 23 | err = s.role.Create(ctx, r) 24 | s.flushCache(ctx) 25 | return 26 | } 27 | 28 | func (s *AuthService) FindRole(ctx context.Context, req *auth.FindRoleRequest) (rp *auth.FindRoleReply, err error) { 29 | tr := otel.Tracer("api") 30 | ctx, span := tr.Start(ctx, "FindRole") 31 | defer span.End() 32 | rp = &auth.FindRoleReply{} 33 | rp.Page = ¶ms.Page{} 34 | r := &biz.FindRole{} 35 | r.Page = page.Page{} 36 | copierx.Copy(&r, req) 37 | copierx.Copy(&r.Page, req.Page) 38 | res, err := s.role.Find(ctx, r) 39 | if err != nil { 40 | return 41 | } 42 | copierx.Copy(&rp.Page, r.Page) 43 | copierx.Copy(&rp.List, res) 44 | return 45 | } 46 | 47 | func (s *AuthService) UpdateRole(ctx context.Context, req *auth.UpdateRoleRequest) (rp *emptypb.Empty, err error) { 48 | tr := otel.Tracer("api") 49 | ctx, span := tr.Start(ctx, "UpdateRole") 50 | defer span.End() 51 | rp = &emptypb.Empty{} 52 | r := &biz.UpdateRole{} 53 | copierx.Copy(&r, req) 54 | err = s.role.Update(ctx, r) 55 | if err != nil { 56 | return 57 | } 58 | s.flushCache(ctx) 59 | return 60 | } 61 | 62 | func (s *AuthService) DeleteRole(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 63 | tr := otel.Tracer("api") 64 | ctx, span := tr.Start(ctx, "DeleteRole") 65 | defer span.End() 66 | rp = &emptypb.Empty{} 67 | err = s.role.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 68 | if err != nil { 69 | return 70 | } 71 | s.flushCache(ctx) 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "auth/api/auth" 5 | "auth/internal/biz" 6 | "auth/internal/conf" 7 | "github.com/go-cinch/common/worker" 8 | "github.com/google/wire" 9 | ) 10 | 11 | // ProviderSet is service providers. 12 | var ProviderSet = wire.NewSet(NewAuthService) 13 | 14 | // AuthService is a auth service. 15 | type AuthService struct { 16 | auth.UnimplementedAuthServer 17 | 18 | c *conf.Bootstrap 19 | task *worker.Worker 20 | user *biz.UserUseCase 21 | action *biz.ActionUseCase 22 | role *biz.RoleUseCase 23 | userGroup *biz.UserGroupUseCase 24 | permission *biz.PermissionUseCase 25 | whitelist *biz.WhitelistUseCase 26 | } 27 | 28 | // NewAuthService new an auth service. 29 | func NewAuthService( 30 | c *conf.Bootstrap, 31 | task *worker.Worker, 32 | user *biz.UserUseCase, 33 | action *biz.ActionUseCase, 34 | role *biz.RoleUseCase, 35 | userGroup *biz.UserGroupUseCase, 36 | permission *biz.PermissionUseCase, 37 | whitelist *biz.WhitelistUseCase, 38 | ) *AuthService { 39 | return &AuthService{ 40 | c: c, 41 | task: task, 42 | user: user, 43 | action: action, 44 | role: role, 45 | userGroup: userGroup, 46 | permission: permission, 47 | whitelist: whitelist, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/auth" 7 | "auth/internal/biz" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/page" 10 | "github.com/go-cinch/common/proto/params" 11 | "github.com/go-cinch/common/utils" 12 | "github.com/golang-module/carbon/v2" 13 | "go.opentelemetry.io/otel" 14 | "google.golang.org/protobuf/types/known/emptypb" 15 | ) 16 | 17 | func (s *AuthService) FindUser(ctx context.Context, req *auth.FindUserRequest) (rp *auth.FindUserReply, err error) { 18 | tr := otel.Tracer("api") 19 | ctx, span := tr.Start(ctx, "FindUser") 20 | defer span.End() 21 | rp = &auth.FindUserReply{} 22 | rp.Page = ¶ms.Page{} 23 | r := &biz.FindUser{} 24 | r.Page = page.Page{} 25 | copierx.Copy(&r, req) 26 | copierx.Copy(&r.Page, req.Page) 27 | res, err := s.user.Find(ctx, r) 28 | if err != nil { 29 | return 30 | } 31 | copierx.Copy(&rp.Page, r.Page) 32 | copierx.Copy(&rp.List, res) 33 | return 34 | } 35 | 36 | func (s *AuthService) UpdateUser(ctx context.Context, req *auth.UpdateUserRequest) (rp *emptypb.Empty, err error) { 37 | tr := otel.Tracer("api") 38 | ctx, span := tr.Start(ctx, "UpdateUser") 39 | defer span.End() 40 | rp = &emptypb.Empty{} 41 | r := &biz.UpdateUser{} 42 | copierx.Copy(&r, req) 43 | if req.LockExpireTime != nil { 44 | lockExpire := carbon.Parse(*req.LockExpireTime).Timestamp() 45 | r.LockExpire = &lockExpire 46 | } 47 | err = s.user.Update(ctx, r) 48 | s.flushCache(ctx) 49 | return 50 | } 51 | 52 | func (s *AuthService) DeleteUser(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 53 | tr := otel.Tracer("api") 54 | ctx, span := tr.Start(ctx, "DeleteUser") 55 | defer span.End() 56 | rp = &emptypb.Empty{} 57 | err = s.user.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 58 | s.flushCache(ctx) 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /internal/service/user_group.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/auth" 7 | "auth/internal/biz" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/page" 10 | "github.com/go-cinch/common/proto/params" 11 | "github.com/go-cinch/common/utils" 12 | "go.opentelemetry.io/otel" 13 | "google.golang.org/protobuf/types/known/emptypb" 14 | ) 15 | 16 | func (s *AuthService) CreateUserGroup(ctx context.Context, req *auth.CreateUserGroupRequest) (rp *emptypb.Empty, err error) { 17 | tr := otel.Tracer("api") 18 | ctx, span := tr.Start(ctx, "CreateUserGroup") 19 | defer span.End() 20 | rp = &emptypb.Empty{} 21 | r := &biz.UserGroup{} 22 | copierx.Copy(&r, req) 23 | err = s.userGroup.Create(ctx, r) 24 | s.flushCache(ctx) 25 | return 26 | } 27 | 28 | func (s *AuthService) FindUserGroup(ctx context.Context, req *auth.FindUserGroupRequest) (rp *auth.FindUserGroupReply, err error) { 29 | tr := otel.Tracer("api") 30 | ctx, span := tr.Start(ctx, "FindUserGroup") 31 | defer span.End() 32 | rp = &auth.FindUserGroupReply{} 33 | rp.Page = ¶ms.Page{} 34 | r := &biz.FindUserGroup{} 35 | r.Page = page.Page{} 36 | copierx.Copy(&r, req) 37 | copierx.Copy(&r.Page, req.Page) 38 | res, err := s.userGroup.Find(ctx, r) 39 | if err != nil { 40 | return 41 | } 42 | copierx.Copy(&rp.Page, r.Page) 43 | copierx.Copy(&rp.List, res) 44 | return 45 | } 46 | 47 | func (s *AuthService) UpdateUserGroup(ctx context.Context, req *auth.UpdateUserGroupRequest) (rp *emptypb.Empty, err error) { 48 | tr := otel.Tracer("api") 49 | ctx, span := tr.Start(ctx, "UpdateUserGroup") 50 | defer span.End() 51 | rp = &emptypb.Empty{} 52 | r := &biz.UpdateUserGroup{} 53 | copierx.Copy(&r, req) 54 | err = s.userGroup.Update(ctx, r) 55 | s.flushCache(ctx) 56 | return 57 | } 58 | 59 | func (s *AuthService) DeleteUserGroup(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 60 | tr := otel.Tracer("api") 61 | ctx, span := tr.Start(ctx, "DeleteUserGroup") 62 | defer span.End() 63 | rp = &emptypb.Empty{} 64 | err = s.userGroup.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 65 | s.flushCache(ctx) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/service/whitelist.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "auth/api/auth" 7 | "auth/internal/biz" 8 | "github.com/go-cinch/common/copierx" 9 | "github.com/go-cinch/common/page" 10 | "github.com/go-cinch/common/proto/params" 11 | "github.com/go-cinch/common/utils" 12 | "go.opentelemetry.io/otel" 13 | "google.golang.org/protobuf/types/known/emptypb" 14 | ) 15 | 16 | func (s *AuthService) CreateWhitelist(ctx context.Context, req *auth.CreateWhitelistRequest) (rp *emptypb.Empty, err error) { 17 | tr := otel.Tracer("api") 18 | ctx, span := tr.Start(ctx, "CreateWhitelist") 19 | defer span.End() 20 | rp = &emptypb.Empty{} 21 | r := &biz.Whitelist{} 22 | copierx.Copy(&r, req) 23 | err = s.whitelist.Create(ctx, r) 24 | s.flushCache(ctx) 25 | return 26 | } 27 | 28 | func (s *AuthService) FindWhitelist(ctx context.Context, req *auth.FindWhitelistRequest) (rp *auth.FindWhitelistReply, err error) { 29 | tr := otel.Tracer("api") 30 | ctx, span := tr.Start(ctx, "FindWhitelist") 31 | defer span.End() 32 | rp = &auth.FindWhitelistReply{} 33 | rp.Page = ¶ms.Page{} 34 | r := &biz.FindWhitelist{} 35 | r.Page = page.Page{} 36 | copierx.Copy(&r, req) 37 | copierx.Copy(&r.Page, req.Page) 38 | res := s.whitelist.Find(ctx, r) 39 | copierx.Copy(&rp.Page, r.Page) 40 | copierx.Copy(&rp.List, res) 41 | return 42 | } 43 | 44 | func (s *AuthService) UpdateWhitelist(ctx context.Context, req *auth.UpdateWhitelistRequest) (rp *emptypb.Empty, err error) { 45 | tr := otel.Tracer("api") 46 | ctx, span := tr.Start(ctx, "UpdateWhitelist") 47 | defer span.End() 48 | rp = &emptypb.Empty{} 49 | r := &biz.UpdateWhitelist{} 50 | copierx.Copy(&r, req) 51 | err = s.whitelist.Update(ctx, r) 52 | s.flushCache(ctx) 53 | return 54 | } 55 | 56 | func (s *AuthService) DeleteWhitelist(ctx context.Context, req *params.IdsRequest) (rp *emptypb.Empty, err error) { 57 | tr := otel.Tracer("api") 58 | ctx, span := tr.Start(ctx, "DeleteWhitelist") 59 | defer span.End() 60 | rp = &emptypb.Empty{} 61 | err = s.whitelist.Delete(ctx, utils.Str2Uint64Arr(req.Ids)...) 62 | s.flushCache(ctx) 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /third_party/README.md: -------------------------------------------------------------------------------- 1 | # third_party 2 | -------------------------------------------------------------------------------- /third_party/cinch/params/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package params; 4 | 5 | option go_package = "github.com/go-cinch/common/proto/params;params"; 6 | option java_multiple_files = true; 7 | option java_package = "com.github.go-cinch.params"; 8 | option objc_class_prefix = "CinchParams"; 9 | 10 | message Page { 11 | uint64 num = 1; 12 | uint64 size = 2; 13 | int64 total = 3; 14 | bool disable = 4; 15 | } 16 | 17 | message IdsRequest { 18 | string ids = 1; 19 | } 20 | -------------------------------------------------------------------------------- /third_party/errors/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package errors; 4 | 5 | option go_package = "github.com/go-kratos/kratos/v2/errors;errors"; 6 | option java_multiple_files = true; 7 | option java_package = "com.github.kratos.errors"; 8 | option objc_class_prefix = "KratosErrors"; 9 | 10 | import "google/protobuf/descriptor.proto"; 11 | 12 | extend google.protobuf.EnumOptions { 13 | int32 default_code = 1108; 14 | } 15 | 16 | extend google.protobuf.EnumValueOptions { 17 | int32 code = 1109; 18 | } -------------------------------------------------------------------------------- /third_party/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /third_party/google/api/client.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/descriptor.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "ClientProto"; 24 | option java_package = "com.google.api"; 25 | option objc_class_prefix = "GAPI"; 26 | 27 | 28 | extend google.protobuf.ServiceOptions { 29 | // The hostname for this service. 30 | // This should be specified with no prefix or protocol. 31 | // 32 | // Example: 33 | // 34 | // service Foo { 35 | // option (google.api.default_host) = "foo.googleapi.com"; 36 | // ... 37 | // } 38 | string default_host = 1049; 39 | 40 | // OAuth scopes needed for the client. 41 | // 42 | // Example: 43 | // 44 | // service Foo { 45 | // option (google.api.oauth_scopes) = \ 46 | // "https://www.googleapis.com/auth/cloud-platform"; 47 | // ... 48 | // } 49 | // 50 | // If there is more than one scope, use a comma-separated string: 51 | // 52 | // Example: 53 | // 54 | // service Foo { 55 | // option (google.api.oauth_scopes) = \ 56 | // "https://www.googleapis.com/auth/cloud-platform," 57 | // "https://www.googleapis.com/auth/monitoring"; 58 | // ... 59 | // } 60 | string oauth_scopes = 1050; 61 | } 62 | 63 | 64 | extend google.protobuf.MethodOptions { 65 | // A definition of a client library method signature. 66 | // 67 | // In client libraries, each proto RPC corresponds to one or more methods 68 | // which the end user is able to call, and calls the underlying RPC. 69 | // Normally, this method receives a single argument (a struct or instance 70 | // corresponding to the RPC request object). Defining this field will 71 | // add one or more overloads providing flattened or simpler method signatures 72 | // in some languages. 73 | // 74 | // The fields on the method signature are provided as a comma-separated 75 | // string. 76 | // 77 | // For example, the proto RPC and annotation: 78 | // 79 | // rpc CreateSubscription(CreateSubscriptionRequest) 80 | // returns (Subscription) { 81 | // option (google.api.method_signature) = "name,topic"; 82 | // } 83 | // 84 | // Would add the following Java overload (in addition to the method accepting 85 | // the request object): 86 | // 87 | // public final Subscription createSubscription(String name, String topic) 88 | // 89 | // The following backwards-compatibility guidelines apply: 90 | // 91 | // * Adding this annotation to an unannotated method is backwards 92 | // compatible. 93 | // * Adding this annotation to a method which already has existing 94 | // method signature annotations is backwards compatible if and only if 95 | // the new method signature annotation is last in the sequence. 96 | // * Modifying or removing an existing method signature annotation is 97 | // a breaking change. 98 | // * Re-ordering existing method signature annotations is a breaking 99 | // change. 100 | repeated string method_signature = 1051; 101 | } -------------------------------------------------------------------------------- /third_party/google/api/field_behavior.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/descriptor.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "FieldBehaviorProto"; 24 | option java_package = "com.google.api"; 25 | option objc_class_prefix = "GAPI"; 26 | 27 | 28 | // An indicator of the behavior of a given field (for example, that a field 29 | // is required in requests, or given as output but ignored as input). 30 | // This **does not** change the behavior in protocol buffers itself; it only 31 | // denotes the behavior and may affect how API tooling handles the field. 32 | // 33 | // Note: This enum **may** receive new values in the future. 34 | enum FieldBehavior { 35 | // Conventional default for enums. Do not use this. 36 | FIELD_BEHAVIOR_UNSPECIFIED = 0; 37 | 38 | // Specifically denotes a field as optional. 39 | // While all fields in protocol buffers are optional, this may be specified 40 | // for emphasis if appropriate. 41 | OPTIONAL = 1; 42 | 43 | // Denotes a field as required. 44 | // This indicates that the field **must** be provided as part of the request, 45 | // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). 46 | REQUIRED = 2; 47 | 48 | // Denotes a field as output only. 49 | // This indicates that the field is provided in responses, but including the 50 | // field in a request does nothing (the server *must* ignore it and 51 | // *must not* throw an error as a result of the field's presence). 52 | OUTPUT_ONLY = 3; 53 | 54 | // Denotes a field as input only. 55 | // This indicates that the field is provided in requests, and the 56 | // corresponding field is not included in output. 57 | INPUT_ONLY = 4; 58 | 59 | // Denotes a field as immutable. 60 | // This indicates that the field may be set once in a request to create a 61 | // resource, but may not be changed thereafter. 62 | IMMUTABLE = 5; 63 | } 64 | 65 | 66 | extend google.protobuf.FieldOptions { 67 | // A designation of a specific field behavior (required, output only, etc.) 68 | // in protobuf messages. 69 | // 70 | // Examples: 71 | // 72 | // string name = 1 [(google.api.field_behavior) = REQUIRED]; 73 | // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; 74 | // google.protobuf.Duration ttl = 1 75 | // [(google.api.field_behavior) = INPUT_ONLY]; 76 | // google.protobuf.Timestamp expire_time = 1 77 | // [(google.api.field_behavior) = OUTPUT_ONLY, 78 | // (google.api.field_behavior) = IMMUTABLE]; 79 | repeated FieldBehavior field_behavior = 1052; 80 | } -------------------------------------------------------------------------------- /third_party/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "HttpBodyProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | // Message that represents an arbitrary HTTP body. It should only be used for 29 | // payload formats that can't be represented as JSON, such as raw binary or 30 | // an HTML page. 31 | // 32 | // 33 | // This message can be used both in streaming and non-streaming API methods in 34 | // the request as well as the response. 35 | // 36 | // It can be used as a top-level request field, which is convenient if one 37 | // wants to extract parameters from either the URL or HTTP template into the 38 | // request fields and also want access to the raw HTTP body. 39 | // 40 | // Example: 41 | // 42 | // message GetResourceRequest { 43 | // // A unique request id. 44 | // string request_id = 1; 45 | // 46 | // // The raw HTTP body is bound to this field. 47 | // google.api.HttpBody http_body = 2; 48 | // } 49 | // 50 | // service ResourceService { 51 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 52 | // rpc UpdateResource(google.api.HttpBody) returns 53 | // (google.protobuf.Empty); 54 | // } 55 | // 56 | // Example with streaming methods: 57 | // 58 | // service CaldavService { 59 | // rpc GetCalendar(stream google.api.HttpBody) 60 | // returns (stream google.api.HttpBody); 61 | // rpc UpdateCalendar(stream google.api.HttpBody) 62 | // returns (stream google.api.HttpBody); 63 | // } 64 | // 65 | // Use of this type only changes how the request and response bodies are 66 | // handled, all other features will continue to work unchanged. 67 | message HttpBody { 68 | // The HTTP Content-Type header value specifying the content type of the body. 69 | string content_type = 1; 70 | 71 | // The HTTP request/response body as raw binary. 72 | bytes data = 2; 73 | 74 | // Application specific response metadata. Must be set in the first response 75 | // for streaming APIs. 76 | repeated google.protobuf.Any extensions = 3; 77 | } 78 | -------------------------------------------------------------------------------- /third_party/google/protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/anypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := anypb.New(foo) 81 | // if err != nil { 82 | // ... 83 | // } 84 | // ... 85 | // foo := &pb.Foo{} 86 | // if err := any.UnmarshalTo(foo); err != nil { 87 | // ... 88 | // } 89 | // 90 | // The pack methods provided by protobuf library will by default use 91 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 92 | // methods only use the fully qualified type name after the last '/' 93 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 94 | // name "y.z". 95 | // 96 | // 97 | // JSON 98 | // 99 | // The JSON representation of an `Any` value uses the regular 100 | // representation of the deserialized, embedded message, with an 101 | // additional field `@type` which contains the type URL. Example: 102 | // 103 | // package google.profile; 104 | // message Person { 105 | // string first_name = 1; 106 | // string last_name = 2; 107 | // } 108 | // 109 | // { 110 | // "@type": "type.googleapis.com/google.profile.Person", 111 | // "firstName": , 112 | // "lastName": 113 | // } 114 | // 115 | // If the embedded message type is well-known and has a custom JSON 116 | // representation, that representation will be embedded adding a field 117 | // `value` which holds the custom JSON in addition to the `@type` 118 | // field. Example (for message [google.protobuf.Duration][]): 119 | // 120 | // { 121 | // "@type": "type.googleapis.com/google.protobuf.Duration", 122 | // "value": "1.212s" 123 | // } 124 | // 125 | message Any { 126 | // A URL/resource name that uniquely identifies the type of the serialized 127 | // protocol buffer message. This string must contain at least 128 | // one "/" character. The last segment of the URL's path must represent 129 | // the fully qualified name of the type (as in 130 | // `path/google.protobuf.Duration`). The name should be in a canonical form 131 | // (e.g., leading "." is not accepted). 132 | // 133 | // In practice, teams usually precompile into the binary all types that they 134 | // expect it to use in the context of Any. However, for URLs which use the 135 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 136 | // server that maps type URLs to message definitions as follows: 137 | // 138 | // * If no scheme is provided, `https` is assumed. 139 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 140 | // value in binary format, or produce an error. 141 | // * Applications are allowed to cache lookup results based on the 142 | // URL, or have them precompiled into a binary to avoid any 143 | // lookup. Therefore, binary compatibility needs to be preserved 144 | // on changes to types. (Use versioned type names to manage 145 | // breaking changes.) 146 | // 147 | // Note: this functionality is not currently available in the official 148 | // protobuf release, and it is not used for type URLs beginning with 149 | // type.googleapis.com. 150 | // 151 | // Schemes other than `http`, `https` (or the empty scheme) might be 152 | // used with implementation specific semantics. 153 | // 154 | string type_url = 1; 155 | 156 | // Must be a valid serialized protocol buffer of the above specified type. 157 | bytes value = 2; 158 | } 159 | -------------------------------------------------------------------------------- /third_party/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/durationpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (duration.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | // Signed seconds of the span of time. Must be from -315,576,000,000 105 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 106 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 107 | int64 seconds = 1; 108 | 109 | // Signed fractions of a second at nanosecond resolution of the span 110 | // of time. Durations less than one second are represented with a 0 111 | // `seconds` field and a positive or negative `nanos` field. For durations 112 | // of one second or more, a non-zero value for the `nanos` field must be 113 | // of the same sign as the `seconds` field. Must be from -999,999,999 114 | // to +999,999,999 inclusive. 115 | int32 nanos = 2; 116 | } 117 | -------------------------------------------------------------------------------- /third_party/google/protobuf/empty.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/emptypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "EmptyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option cc_enable_arenas = true; 42 | 43 | // A generic empty message that you can re-use to avoid defining duplicated 44 | // empty messages in your APIs. A typical example is to use it as the request 45 | // or the response type of an API method. For instance: 46 | // 47 | // service Foo { 48 | // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 49 | // } 50 | // 51 | // The JSON representation for `Empty` is empty JSON object `{}`. 52 | message Empty {} 53 | -------------------------------------------------------------------------------- /third_party/google/protobuf/source_context.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "SourceContextProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; 41 | 42 | // `SourceContext` represents information about the source of a 43 | // protobuf element, like the file in which it is defined. 44 | message SourceContext { 45 | // The path-qualified name of the .proto file that contained the associated 46 | // protobuf element. For example: `"google/protobuf/source_context.proto"`. 47 | string file_name = 1; 48 | } 49 | -------------------------------------------------------------------------------- /third_party/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // `Struct` represents a structured data value, consisting of fields 44 | // which map to dynamically typed values. In some languages, `Struct` 45 | // might be supported by a native representation. For example, in 46 | // scripting languages like JS a struct is represented as an 47 | // object. The details of that representation are described together 48 | // with the proto support for the language. 49 | // 50 | // The JSON representation for `Struct` is JSON object. 51 | message Struct { 52 | // Unordered map of dynamically typed values. 53 | map fields = 1; 54 | } 55 | 56 | // `Value` represents a dynamically typed value which can be either 57 | // null, a number, a string, a boolean, a recursive struct value, or a 58 | // list of values. A producer of value is expected to set one of these 59 | // variants. Absence of any variant indicates an error. 60 | // 61 | // The JSON representation for `Value` is JSON value. 62 | message Value { 63 | // The kind of value. 64 | oneof kind { 65 | // Represents a null value. 66 | NullValue null_value = 1; 67 | // Represents a double value. 68 | double number_value = 2; 69 | // Represents a string value. 70 | string string_value = 3; 71 | // Represents a boolean value. 72 | bool bool_value = 4; 73 | // Represents a structured value. 74 | Struct struct_value = 5; 75 | // Represents a repeated `Value`. 76 | ListValue list_value = 6; 77 | } 78 | } 79 | 80 | // `NullValue` is a singleton enumeration to represent the null value for the 81 | // `Value` type union. 82 | // 83 | // The JSON representation for `NullValue` is JSON `null`. 84 | enum NullValue { 85 | // Null value. 86 | NULL_VALUE = 0; 87 | } 88 | 89 | // `ListValue` is a wrapper around a repeated field of values. 90 | // 91 | // The JSON representation for `ListValue` is JSON array. 92 | message ListValue { 93 | // Repeated field of dynamically typed values. 94 | repeated Value values = 1; 95 | } 96 | -------------------------------------------------------------------------------- /third_party/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/timestamppb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone or local 44 | // calendar, encoded as a count of seconds and fractions of seconds at 45 | // nanosecond resolution. The count is relative to an epoch at UTC midnight on 46 | // January 1, 1970, in the proleptic Gregorian calendar which extends the 47 | // Gregorian calendar backwards to year one. 48 | // 49 | // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 50 | // second table is needed for interpretation, using a [24-hour linear 51 | // smear](https://developers.google.com/time/smear). 52 | // 53 | // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 54 | // restricting to that range, we ensure that we can convert to and from [RFC 55 | // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 56 | // 57 | // # Examples 58 | // 59 | // Example 1: Compute Timestamp from POSIX `time()`. 60 | // 61 | // Timestamp timestamp; 62 | // timestamp.set_seconds(time(NULL)); 63 | // timestamp.set_nanos(0); 64 | // 65 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 66 | // 67 | // struct timeval tv; 68 | // gettimeofday(&tv, NULL); 69 | // 70 | // Timestamp timestamp; 71 | // timestamp.set_seconds(tv.tv_sec); 72 | // timestamp.set_nanos(tv.tv_usec * 1000); 73 | // 74 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 75 | // 76 | // FILETIME ft; 77 | // GetSystemTimeAsFileTime(&ft); 78 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 79 | // 80 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 81 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 82 | // Timestamp timestamp; 83 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 84 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 85 | // 86 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 87 | // 88 | // long millis = System.currentTimeMillis(); 89 | // 90 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 91 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 92 | // 93 | // 94 | // Example 5: Compute Timestamp from Java `Instant.now()`. 95 | // 96 | // Instant now = Instant.now(); 97 | // 98 | // Timestamp timestamp = 99 | // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 100 | // .setNanos(now.getNano()).build(); 101 | // 102 | // 103 | // Example 6: Compute Timestamp from current time in Python. 104 | // 105 | // timestamp = Timestamp() 106 | // timestamp.GetCurrentTime() 107 | // 108 | // # JSON Mapping 109 | // 110 | // In JSON format, the Timestamp type is encoded as a string in the 111 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 112 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 113 | // where {year} is always expressed using four digits while {month}, {day}, 114 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 115 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 116 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 117 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 118 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 119 | // able to accept both UTC and other timezones (as indicated by an offset). 120 | // 121 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 122 | // 01:30 UTC on January 15, 2017. 123 | // 124 | // In JavaScript, one can convert a Date object to this format using the 125 | // standard 126 | // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 127 | // method. In Python, a standard `datetime.datetime` object can be converted 128 | // to this format using 129 | // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 130 | // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 131 | // the Joda Time's [`ISODateTimeFormat.dateTime()`]( 132 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D 133 | // ) to obtain a formatter capable of generating timestamps in this format. 134 | // 135 | // 136 | message Timestamp { 137 | // Represents seconds of UTC time since Unix epoch 138 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 139 | // 9999-12-31T23:59:59Z inclusive. 140 | int64 seconds = 1; 141 | 142 | // Non-negative fractions of a second at nanosecond resolution. Negative 143 | // second values with fractions must still have non-negative nanos values 144 | // that count forward in time. Must be from 0 to 999,999,999 145 | // inclusive. 146 | int32 nanos = 2; 147 | } 148 | -------------------------------------------------------------------------------- /third_party/google/protobuf/type.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/any.proto"; 36 | import "google/protobuf/source_context.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option cc_enable_arenas = true; 40 | option java_package = "com.google.protobuf"; 41 | option java_outer_classname = "TypeProto"; 42 | option java_multiple_files = true; 43 | option objc_class_prefix = "GPB"; 44 | option go_package = "google.golang.org/protobuf/types/known/typepb"; 45 | 46 | // A protocol buffer message type. 47 | message Type { 48 | // The fully qualified message name. 49 | string name = 1; 50 | // The list of fields. 51 | repeated Field fields = 2; 52 | // The list of types appearing in `oneof` definitions in this type. 53 | repeated string oneofs = 3; 54 | // The protocol buffer options. 55 | repeated Option options = 4; 56 | // The source context. 57 | SourceContext source_context = 5; 58 | // The source syntax. 59 | Syntax syntax = 6; 60 | } 61 | 62 | // A single field of a message type. 63 | message Field { 64 | // Basic field types. 65 | enum Kind { 66 | // Field type unknown. 67 | TYPE_UNKNOWN = 0; 68 | // Field type double. 69 | TYPE_DOUBLE = 1; 70 | // Field type float. 71 | TYPE_FLOAT = 2; 72 | // Field type int64. 73 | TYPE_INT64 = 3; 74 | // Field type uint64. 75 | TYPE_UINT64 = 4; 76 | // Field type int32. 77 | TYPE_INT32 = 5; 78 | // Field type fixed64. 79 | TYPE_FIXED64 = 6; 80 | // Field type fixed32. 81 | TYPE_FIXED32 = 7; 82 | // Field type bool. 83 | TYPE_BOOL = 8; 84 | // Field type string. 85 | TYPE_STRING = 9; 86 | // Field type group. Proto2 syntax only, and deprecated. 87 | TYPE_GROUP = 10; 88 | // Field type message. 89 | TYPE_MESSAGE = 11; 90 | // Field type bytes. 91 | TYPE_BYTES = 12; 92 | // Field type uint32. 93 | TYPE_UINT32 = 13; 94 | // Field type enum. 95 | TYPE_ENUM = 14; 96 | // Field type sfixed32. 97 | TYPE_SFIXED32 = 15; 98 | // Field type sfixed64. 99 | TYPE_SFIXED64 = 16; 100 | // Field type sint32. 101 | TYPE_SINT32 = 17; 102 | // Field type sint64. 103 | TYPE_SINT64 = 18; 104 | } 105 | 106 | // Whether a field is optional, required, or repeated. 107 | enum Cardinality { 108 | // For fields with unknown cardinality. 109 | CARDINALITY_UNKNOWN = 0; 110 | // For optional fields. 111 | CARDINALITY_OPTIONAL = 1; 112 | // For required fields. Proto2 syntax only. 113 | CARDINALITY_REQUIRED = 2; 114 | // For repeated fields. 115 | CARDINALITY_REPEATED = 3; 116 | } 117 | 118 | // The field type. 119 | Kind kind = 1; 120 | // The field cardinality. 121 | Cardinality cardinality = 2; 122 | // The field number. 123 | int32 number = 3; 124 | // The field name. 125 | string name = 4; 126 | // The field type URL, without the scheme, for message or enumeration 127 | // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. 128 | string type_url = 6; 129 | // The index of the field type in `Type.oneofs`, for message or enumeration 130 | // types. The first type has index 1; zero means the type is not in the list. 131 | int32 oneof_index = 7; 132 | // Whether to use alternative packed wire representation. 133 | bool packed = 8; 134 | // The protocol buffer options. 135 | repeated Option options = 9; 136 | // The field JSON name. 137 | string json_name = 10; 138 | // The string value of the default value of this field. Proto2 syntax only. 139 | string default_value = 11; 140 | } 141 | 142 | // Enum type definition. 143 | message Enum { 144 | // Enum type name. 145 | string name = 1; 146 | // Enum value definitions. 147 | repeated EnumValue enumvalue = 2; 148 | // Protocol buffer options. 149 | repeated Option options = 3; 150 | // The source context. 151 | SourceContext source_context = 4; 152 | // The source syntax. 153 | Syntax syntax = 5; 154 | } 155 | 156 | // Enum value definition. 157 | message EnumValue { 158 | // Enum value name. 159 | string name = 1; 160 | // Enum value number. 161 | int32 number = 2; 162 | // Protocol buffer options. 163 | repeated Option options = 3; 164 | } 165 | 166 | // A protocol buffer option, which can be attached to a message, field, 167 | // enumeration, etc. 168 | message Option { 169 | // The option's name. For protobuf built-in options (options defined in 170 | // descriptor.proto), this is the short name. For example, `"map_entry"`. 171 | // For custom options, it should be the fully-qualified name. For example, 172 | // `"google.api.http"`. 173 | string name = 1; 174 | // The option's value packed in an Any message. If the value is a primitive, 175 | // the corresponding wrapper type defined in google/protobuf/wrappers.proto 176 | // should be used. If the value is an enum, it should be stored as an int32 177 | // value using the google.protobuf.Int32Value type. 178 | Any value = 2; 179 | } 180 | 181 | // The syntax in which a protocol buffer element is defined. 182 | enum Syntax { 183 | // Syntax `proto2`. 184 | SYNTAX_PROTO2 = 0; 185 | // Syntax `proto3`. 186 | SYNTAX_PROTO3 = 1; 187 | } 188 | -------------------------------------------------------------------------------- /third_party/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | // 36 | // These wrappers have no meaningful use within repeated fields as they lack 37 | // the ability to detect presence on individual elements. 38 | // These wrappers have no meaningful use within a map or a oneof since 39 | // individual entries of a map or fields of a oneof can already detect presence. 40 | 41 | syntax = "proto3"; 42 | 43 | package google.protobuf; 44 | 45 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 46 | option cc_enable_arenas = true; 47 | option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; 48 | option java_package = "com.google.protobuf"; 49 | option java_outer_classname = "WrappersProto"; 50 | option java_multiple_files = true; 51 | option objc_class_prefix = "GPB"; 52 | 53 | // Wrapper message for `double`. 54 | // 55 | // The JSON representation for `DoubleValue` is JSON number. 56 | message DoubleValue { 57 | // The double value. 58 | double value = 1; 59 | } 60 | 61 | // Wrapper message for `float`. 62 | // 63 | // The JSON representation for `FloatValue` is JSON number. 64 | message FloatValue { 65 | // The float value. 66 | float value = 1; 67 | } 68 | 69 | // Wrapper message for `int64`. 70 | // 71 | // The JSON representation for `Int64Value` is JSON string. 72 | message Int64Value { 73 | // The int64 value. 74 | int64 value = 1; 75 | } 76 | 77 | // Wrapper message for `uint64`. 78 | // 79 | // The JSON representation for `UInt64Value` is JSON string. 80 | message UInt64Value { 81 | // The uint64 value. 82 | uint64 value = 1; 83 | } 84 | 85 | // Wrapper message for `int32`. 86 | // 87 | // The JSON representation for `Int32Value` is JSON number. 88 | message Int32Value { 89 | // The int32 value. 90 | int32 value = 1; 91 | } 92 | 93 | // Wrapper message for `uint32`. 94 | // 95 | // The JSON representation for `UInt32Value` is JSON number. 96 | message UInt32Value { 97 | // The uint32 value. 98 | uint32 value = 1; 99 | } 100 | 101 | // Wrapper message for `bool`. 102 | // 103 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 104 | message BoolValue { 105 | // The bool value. 106 | bool value = 1; 107 | } 108 | 109 | // Wrapper message for `string`. 110 | // 111 | // The JSON representation for `StringValue` is JSON string. 112 | message StringValue { 113 | // The string value. 114 | string value = 1; 115 | } 116 | 117 | // Wrapper message for `bytes`. 118 | // 119 | // The JSON representation for `BytesValue` is JSON string. 120 | message BytesValue { 121 | // The bytes value. 122 | bytes value = 1; 123 | } 124 | -------------------------------------------------------------------------------- /third_party/openapiv3/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package openapi.v3; 18 | 19 | import "openapiv3/OpenAPIv3.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | // This option lets the proto compiler generate Java code inside the package 23 | // name (see below) instead of inside an outer class. It creates a simpler 24 | // developer experience by reducing one-level of name nesting and be 25 | // consistent with most programming languages that don't support outer classes. 26 | option java_multiple_files = true; 27 | 28 | // The Java outer classname should be the filename in UpperCamelCase. This 29 | // class is only used to hold proto descriptor, so developers don't need to 30 | // work with it directly. 31 | option java_outer_classname = "AnnotationsProto"; 32 | 33 | // The Java package name must be proto package name with proper prefix. 34 | option java_package = "org.openapi_v3"; 35 | 36 | // A reasonable prefix for the Objective-C symbols generated from the package. 37 | // It should at a minimum be 3 characters long, all uppercase, and convention 38 | // is to use an abbreviation of the package name. Something short, but 39 | // hopefully unique enough to not conflict with things that may come along in 40 | // the future. 'GPB' is reserved for the protocol buffer implementation itself. 41 | option objc_class_prefix = "OAS"; 42 | 43 | // The Go package name. 44 | option go_package = "github.com/google/gnostic/openapiv3;openapi_v3"; 45 | 46 | extend google.protobuf.FileOptions { 47 | Document document = 1143; 48 | } 49 | 50 | extend google.protobuf.MethodOptions { 51 | Operation operation = 1143; 52 | } 53 | 54 | extend google.protobuf.MessageOptions { 55 | Schema schema = 1143; 56 | } 57 | 58 | extend google.protobuf.FieldOptions { 59 | Schema property = 1143; 60 | } -------------------------------------------------------------------------------- /third_party/validate/README.md: -------------------------------------------------------------------------------- 1 | # protoc-gen-validate (PGV) 2 | 3 | * https://github.com/envoyproxy/protoc-gen-validate 4 | --------------------------------------------------------------------------------