├── .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 |
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 | # 当前版本
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 |
--------------------------------------------------------------------------------