mirror of
https://github.com/ssep1ol/overleaf-registration-be.git
synced 2026-07-21 12:30:21 +00:00
First commit
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALLOWED_DOMAINS=xyz.com,abc.de,fgh.il
|
||||||
|
CAPTCHA_SERVER_KEY=
|
||||||
|
OL_INSTANCE=
|
||||||
|
OL_ADMIN_EMAIL=
|
||||||
|
OL_ADMIN_PASSWORD=
|
||||||
|
PORT=3000
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM golang:1.20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go mod tidy
|
||||||
|
RUN go build -o app .
|
||||||
|
|
||||||
|
CMD ["./app"]
|
||||||
32
captcha.go
Normal file
32
captcha.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CaptchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateCaptcha(captcha string) bool {
|
||||||
|
secretKey := os.Getenv("CAPTCHA_SERVER_KEY")
|
||||||
|
if secretKey == "" {
|
||||||
|
return true // Skip validation if CAPTCHA is disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", url.Values{
|
||||||
|
"secret": {secretKey},
|
||||||
|
"response": {captcha},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var result CaptchaResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||||
|
return err == nil && result.Success
|
||||||
|
}
|
||||||
73
handlers.go
Normal file
73
handlers.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Captcha string `json:"captcha"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the request
|
||||||
|
var req RegisterRequest
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate email domain
|
||||||
|
allowedDomains := strings.Split(os.Getenv("ALLOWED_DOMAINS"), ",")
|
||||||
|
if !isDomainAllowed(req.Email, allowedDomains) {
|
||||||
|
http.Error(w, "Email domain not allowed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CAPTCHA
|
||||||
|
if !ValidateCaptcha(req.Captcha) {
|
||||||
|
http.Error(w, "Invalid CAPTCHA", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register user
|
||||||
|
overleaf := NewOverleaf(os.Getenv("OL_INSTANCE"))
|
||||||
|
err = overleaf.Login(os.Getenv("OL_ADMIN_EMAIL"), os.Getenv("OL_ADMIN_PASSWORD"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to log in as admin", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = overleaf.RegisterUser(req.Email, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to register user: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"message": "User registered successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDomainAllowed(email string, allowedDomains []string) bool {
|
||||||
|
parts := strings.Split(email, "@")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
domain := parts[1]
|
||||||
|
for _, allowed := range allowedDomains {
|
||||||
|
if domain == allowed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
28
main.go
Normal file
28
main.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load environment variables
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("No .env file found, using environment variables")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the HTTP handler
|
||||||
|
http.HandleFunc("/register", RegisterHandler)
|
||||||
|
|
||||||
|
// Start the HTTP server
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "3000"
|
||||||
|
}
|
||||||
|
log.Printf("Server running on port %s\n", port)
|
||||||
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||||
|
}
|
||||||
118
overleaf.go
Normal file
118
overleaf.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Overleaf struct {
|
||||||
|
BaseURL string
|
||||||
|
Client *http.Client
|
||||||
|
CSRF string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOverleaf initializes a new Overleaf instance
|
||||||
|
func NewOverleaf(baseURL string) *Overleaf {
|
||||||
|
return &Overleaf{
|
||||||
|
BaseURL: strings.TrimSuffix(baseURL, "/"),
|
||||||
|
Client: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get performs a GET request
|
||||||
|
func (o *Overleaf) get(endpoint string) (*http.Response, error) {
|
||||||
|
url := fmt.Sprintf("%s%s", o.BaseURL, endpoint)
|
||||||
|
return o.Client.Get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// post performs a POST request
|
||||||
|
func (o *Overleaf) post(endpoint string, data map[string]string) (*http.Response, error) {
|
||||||
|
if o.CSRF == "" {
|
||||||
|
if err := o.obtainCSRF(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s%s", o.BaseURL, endpoint)
|
||||||
|
data["_csrf"] = o.CSRF
|
||||||
|
formData := encodeFormData(data)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBufferString(formData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
return o.Client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtainCSRF retrieves the CSRF token
|
||||||
|
func (o *Overleaf) obtainCSRF() error {
|
||||||
|
resp, err := o.get("/login")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
csrf, exists := doc.Find(`meta[name="ol-csrfToken"]`).Attr("content")
|
||||||
|
if !exists {
|
||||||
|
return errors.New("CSRF token not found")
|
||||||
|
}
|
||||||
|
o.CSRF = csrf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login as admin
|
||||||
|
func (o *Overleaf) Login(email, password string) error {
|
||||||
|
resp, err := o.post("/login", map[string]string{
|
||||||
|
"email": email,
|
||||||
|
"password": password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New("failed to log in as admin")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUser registers a new user
|
||||||
|
func (o *Overleaf) RegisterUser(email, password string) error {
|
||||||
|
resp, err := o.post("/admin/register", map[string]string{
|
||||||
|
"email": email,
|
||||||
|
"password": password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("failed to register user: %s", string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeFormData encodes a map into x-www-form-urlencoded format
|
||||||
|
func encodeFormData(data map[string]string) string {
|
||||||
|
var formData []string
|
||||||
|
for key, value := range data {
|
||||||
|
formData = append(formData, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
return strings.Join(formData, "&")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user