Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update readme #76

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
);
Loading