Skip to content

Commit

Permalink
Merge pull request #76 from NJUPT-SAST/update-readme
Browse files Browse the repository at this point in the history
Update readme
  • Loading branch information
Xunop authored Mar 14, 2024
2 parents 3023a09 + 08cd081 commit 4e7b596
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 56 deletions.
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,107 @@
# sast-link-backend
# SAST Link

[SAST Link Logo](https://aliyun.sastimg.mxte.cc/images/2023/07/02/footera9663bd5ff4b2bad.png)

Logo designed by [SAST](https://sast.fun/), created by [Maxtune Lee](https://github.com/MaxtuneLee).

[![Go Report Card](https://goreportcard.com/badge/github.com/NJUPT-SAST/sast-link-backend)](https://goreportcard.com/report/github.com/NJUPT-SAST/sast-link-backend)
[![License](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://choosealicense.com/licenses/agpl-3.0/)

SAST Link is a comprehensive personnel management system and OAuth designed to provide a secure and efficient way to manage and authorize access to your applications and services.

Product design in Figma: [SAST Link](https://www.figma.com/file/IUIoRll3ieYFzJSfJPelDu/sast-link?node-id=0-1&t=rtc1sJfjJ0aTDAkp-0), designed by [Maxtune Lee](https://github.com/MaxtuneLee)

This repository contains the backend code for SAST Link. If you're interested in the frontend, please visit [SAST Link frontend](https://github.com/NJUPT-SAST/sast-link).

SAST Link backend is built with Go and PostgreSQL, and use gin as the web framework.

> [!WARNING]
> This repo is under active development! Formats, schemas, and APIs are subject to rapid and backward incompatible changes!
## Getting Started

### Pre-requisites

- Go
- PostgreSQL
- Redis
- Email Account (SMTP)
- Tencent COS (For file storage)
- Oauth2.0 Provider (e.g. GitHub, Feishu)

Create PostgreSQL database and tables by running the SQL scripts in `sql/` directory.

### Clone and Run

To get started with SAST Link, follow these steps:

1. Configuration: First, create a configuration file based on `config/dev-example.toml`. Ensure that you provide appropriate configurations for your environment.
2. Environment Setup: Set up the environment variable `CONFIG_FILE` to specify the configuration file you've created.
3. Installation and Execution:

```bash
git clone https://github.com/NJUPT-SAST/sast-link-backend.git && cd sast-link-backend
CONFIG_FILE=dev-example go run .
```

The server will listen port `8080`, you can change it by add a `PORT` environment variable.

## Development

### API Documentation

The API documentation is available at [wiki](https://github.com/NJUPT-SAST/sast-link-backend/wiki/Api-Doc)

### Database Schema

The database schema is available at [wiki](https://github.com/NJUPT-SAST/sast-link-backend/wiki/Project-Structure#sql)

### Code Workflow Explanation

The code workflow is available at [wiki](https://github.com/NJUPT-SAST/sast-link-backend/wiki/General)

## Roadmap

Goals and Vision for SAST Link (SAST OAuth and SAST Profile):

**SAST OAuth:**

SAST OAuth serves as a unified identity authentication system for SAST, facilitating login across multiple SAST applications.

Example:

- Simplifies login processes for SAST members across various projects, such as the FreshCup competition.
- Enables seamless login via SAST credentials without the need for separate accounts for each project.
- Allows SAST lecturers to access and manage the FreshCup competition system for tasks like grading via SAST login.
- Offers multiple login options including SAST Feishu, PassKey, QQ, Github, etc., providing users with convenience and flexibility.
- Implements additional security measures like F2A and security keys to enhance account security.

In login process, users can choose to log in in multiple ways: SAST Feishu, PassKey, QQ, Github, etc. As long as they have been bound in advance, they can use third-party login, which is convenient and fast. They can also use F2A, security keys, and other methods to enhance account security.

**SAST Profile:**

SAST Profile acts as a centralized user profile system for managing user information and settings within SAST applications.

Features:

- Records basic user information such as SAST membership status, current position, department, group affiliation, etc.
- Tracks user activities within SAST, including competition results, awards, and permissions across various applications.
- Provides users with the ability to customize and share their profile page, allowing them to control the visibility of their information.

**Current status**:

- [x] User Management (Basic)
- [x] SAST OAuth (Basic)
- [x] File Storage (Tencent COS)
- [x] SAST Profile (Basic)
- [] SAST Link management
- [] Third-party OAuth (Github and Feishu now can be used in backend, but not fully implemente)

## Contributing

Pull requests and any feedback are welcome. For major changes, please open an issue first
to discuss what you would like to change.

## License

[AGPLv3 ](https://choosealicense.com/licenses/agpl-3.0/)
4 changes: 1 addition & 3 deletions api/v1/oauth_client_github.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ func OauthGithubLogin(c *gin.Context) {
oauthState := GenerateStateOauthCookie(c.Writer)
url := githubConf.AuthCodeURL(oauthState)

log.Log.Println("------")
log.Log.Printf("Visit the URL for the auth dialog: %v\n", url)
log.Log.Println("------")
log.Log.Warnf("Visit the URL for the auth dialog: %v\n", url)

c.Redirect(http.StatusFound, url)
}
Expand Down
47 changes: 0 additions & 47 deletions api/v1/oauth_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,20 +206,7 @@ func OauthUserInfo(c *gin.Context) {
func Authorize(c *gin.Context) {
r := c.Request
w := c.Writer
// store, err := session.Start(c, w, r)
// if err != nil {
// c.JSON(http.StatusInternalServerError, result.Failed(result.InternalErr.Wrap(err)))
// return
// }
_ = r.ParseForm()
// var form url.Values
// if v, ok := store.Get("ReturnUri"); ok {
// form = v.(url.Values)
// }
// r.Form = form
// store.Delete("ReturnUri")
// _ = store.Save()

// Redirect user to login page if user not login or
// Get code directly if user has logged in
err := srv.HandleAuthorizeRequest(w, r)
Expand All @@ -229,23 +216,6 @@ func Authorize(c *gin.Context) {
}
}

// User decides whether to authorize
// func UserAuth(c *gin.Context) {
// w := c.Writer
// r := c.Request
//
// //token := r.Header.Get("TOKEN")
// _ = r.ParseMultipartForm(0)
// token := c.PostForm("token")
// if token == "" {
// w.Header().Set("Content-Type", "application/json")
// response := result.Failed(result.AuthError)
// json, _ := json.Marshal(response)
// w.Write(json)
// return
// }
// }

// Get AccessToken
func AccessToken(c *gin.Context) {
w := c.Writer
Expand Down Expand Up @@ -314,11 +284,6 @@ func getTokenByUUID(c context.Context, uuid string) (token string, err error) {
}

func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) {
// session, err := session.Start(r.Context(), w, r)
// if err != nil {
// return
// }

token := r.Form.Get("part")
if token == "" {
if r.Form == nil {
Expand All @@ -327,9 +292,6 @@ func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string
_ = r.ParseForm()
}

// session.Set("ReturnUri", r.Form)
// _ = session.Save()

w.Header().Set("Content-Type", "application/json")
response := result.Failed(result.TokenError)
log.Log.Errorln("Oauth2 ::: token is empty")
Expand All @@ -345,9 +307,6 @@ func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string
_ = r.ParseForm()
}

// session.Set("ReturnUri", r.Form)
// _ = session.Save()

w.Header().Set("Content-Type", "application/json")
response := result.Failed(result.TokenError)
log.Log.Errorln("Oauth2 ::: token is invalid")
Expand All @@ -362,9 +321,6 @@ func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string
_ = r.ParseForm()
}

// session.Set("ReturnUri", r.Form)
// _ = session.Save()

w.Header().Set("Content-Type", "application/json")
response := result.Failed(result.TokenError)
log.Log.Errorln("Oauth2 ::: token is invalid")
Expand All @@ -377,9 +333,6 @@ func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string
_ = r.ParseForm()
}

// session.Set("ReturnUri", r.Form)
// _ = session.Save()

w.Header().Set("Content-Type", "application/json")
response := result.Failed(result.TokenError)
log.Log.Errorln("Oauth2 ::: token is invalid")
Expand Down
4 changes: 0 additions & 4 deletions api/v1/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ func SendEmail(ctx *gin.Context) {
// 我开始乱写了啊啊啊啊
if usernameErr != nil {
controllerLogger.Errorf("username parse error: %s", usernameErr.Error())
//ctx.JSON(http.StatusUnauthorized, result.Failed(result.TicketNotCorrect))
ctx.JSON(http.StatusUnauthorized, result.Failed(result.HandleErrorWithArgu(usernameErr, result.TicketNotCorrect)))
return
}
Expand Down Expand Up @@ -192,16 +191,13 @@ func Login(ctx *gin.Context) {
// Get username from ticket
username, err := util.GetUsername(ticket, model.LOGIN_TICKET_SUB)
if err != nil || username == "" {
//ctx.JSON(http.StatusOK, result.Failed(result.TicketNotCorrect))
ctx.JSON(http.StatusOK, result.Failed(result.HandleErrorWithArgu(err, result.TicketNotCorrect)))
return
}

uid, err := service.Login(username, password)
if err != nil {
controllerLogger.Errorf("login fail: %s", err.Error())
//ctx.JSON(http.StatusUnauthorized, result.Failed(result.VerifyAccountError))
//ctx.AbortWithStatusJSON(http.StatusUnauthorized, result.Failed(result.VerifyPasswordError))
ctx.AbortWithStatusJSON(http.StatusUnauthorized, result.Failed(result.HandleErrorWithArgu(err, result.VerifyPasswordError)))
return
}
Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var Config *viper.Viper = viper.New()
func init() {
fileName := os.Getenv("CONFIG_FILE")
if fileName == "" {
fileName = "dev-xun"
fileName = "dev-prod"
}
Config.AddConfigPath(".")
Config.AddConfigPath("../../config")
Expand Down
12 changes: 12 additions & 0 deletions sql/admin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- public."admin" definition

-- Drop table

-- DROP TABLE public."admin";

CREATE TABLE public."admin" (
id serial4 NOT NULL,
created_at timestamp NOT NULL DEFAULT now(),
user_id varchar(255) NOT NULL,
CONSTRAINT admin_pkey PRIMARY KEY (id)
);
23 changes: 23 additions & 0 deletions sql/carrer_records.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- public.carrer_records definition

-- Drop table

-- DROP TABLE public.carrer_records;

CREATE TABLE public.carrer_records (
id serial4 NOT NULL,
user_id int4 NOT NULL, -- 与user表映射,表示某个用户的生涯记录
org_id int2 NOT NULL, -- 与orgnize表映射,表示用户该届所在的组织
grade int2 NOT NULL, -- 表示某一届(如:2023届)
is_delete bool NOT NULL, -- 假删
"position" varchar(2) NULL, -- 包括:部员、讲师、组长、部长、主席
CONSTRAINT carrer_records_pkey PRIMARY KEY (id)
);

-- Column comments

COMMENT ON COLUMN public.carrer_records.user_id IS '与user表映射,表示某个用户的生涯记录';
COMMENT ON COLUMN public.carrer_records.org_id IS '与orgnize表映射,表示用户该届所在的组织';
COMMENT ON COLUMN public.carrer_records.grade IS '表示某一届(如:2023届)';
COMMENT ON COLUMN public.carrer_records.is_delete IS '假删';
COMMENT ON COLUMN public.carrer_records."position" IS '包括:部员、讲师、组长、部长、主席';
12 changes: 12 additions & 0 deletions sql/organize.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- public.organize definition

-- Drop table

-- DROP TABLE public.organize;

CREATE TABLE public.organize (
id int4 NOT NULL DEFAULT nextval('department_id_seq'::regclass),
dep varchar(255) NOT NULL,
org varchar(255) NULL,
CONSTRAINT department_pkey PRIMARY KEY (id)
);
33 changes: 33 additions & 0 deletions sql/profile.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- public.profile definition

-- Drop table

-- DROP TABLE public.profile;

CREATE TABLE public.profile (
id serial4 NOT NULL,
user_id int4 NOT NULL, -- 与user表映射
nickname varchar(255) NOT NULL, -- 昵称
org_id int2 NOT NULL, -- 对应部门和组的信息(现在的职位,历史职位的信息在carrer_records中)
bio varchar(255) NULL, -- 自我介绍
email varchar(255) NOT NULL, -- 邮箱(默认展示)
badge json NULL, -- 纪念卡
link _varchar NULL, -- 个人链接(包括自己b站、博客、GitHub等账号链接)
avatar varchar(255) NULL, -- 头像(存储oss链接)
is_deleted bool NOT NULL, -- 假删
hide _varchar NULL, -- 选择隐藏的信息
CONSTRAINT profile_pkey PRIMARY KEY (id)
);

-- Column comments

COMMENT ON COLUMN public.profile.user_id IS '与user表映射';
COMMENT ON COLUMN public.profile.nickname IS '昵称';
COMMENT ON COLUMN public.profile.org_id IS '对应部门和组的信息(现在的职位,历史职位的信息在carrer_records中)';
COMMENT ON COLUMN public.profile.bio IS '自我介绍';
COMMENT ON COLUMN public.profile.email IS '邮箱(默认展示)';
COMMENT ON COLUMN public.profile.badge IS '纪念卡';
COMMENT ON COLUMN public.profile.link IS '个人链接(包括自己b站、博客、GitHub等账号链接)';
COMMENT ON COLUMN public.profile.avatar IS '头像(存储oss链接)';
COMMENT ON COLUMN public.profile.is_deleted IS '假删';
COMMENT ON COLUMN public.profile.hide IS '选择隐藏的信息';
19 changes: 19 additions & 0 deletions sql/user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- public."user" definition

-- Drop table

-- DROP TABLE public."user";

CREATE TABLE public."user" (
id serial4 NOT NULL,
created_at timestamp NOT NULL DEFAULT now(),
email varchar(255) NOT NULL,
uid varchar(255) NOT NULL,
qq_id varchar(255) NULL,
lark_id varchar(255) NULL,
github_id varchar(255) NULL,
wechat_id varchar(255) NULL,
is_deleted bool NOT NULL,
"password" varchar(255) NOT NULL,
CONSTRAINT user_pkey PRIMARY KEY (id)
);

0 comments on commit 4e7b596

Please sign in to comment.