Compare commits
No commits in common. "b10bdd756ab609c8069e0bbe9e43f9df81f68984" and "c965c578bf4e81b5bfdcfd46858d210e34a7555a" have entirely different histories.
b10bdd756a
...
c965c578bf
4
TODO
4
TODO
|
@ -1,4 +1,2 @@
|
||||||
- [x] admin check for a user struct
|
- [ ] admin check for a user struct
|
||||||
- [x] gitea requesting
|
|
||||||
- [x] gemini requesting
|
|
||||||
- [ ] get user stats/info
|
- [ ] get user stats/info
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
email "git.tilde.town/tildetown/town/email"
|
|
||||||
)
|
|
||||||
|
|
||||||
const geminiHomeDocBase = "/home/gemini/users"
|
|
||||||
|
|
||||||
func processGemini(requestRootPath string) error {
|
|
||||||
rp := filepath.Join(requestRootPath, "gemini")
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(rp)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to list directory %s: %w", rp, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
usernames := []string{}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
usernames = append(usernames, file.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(usernames) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, username := range usernames {
|
|
||||||
err := linkGemini(username)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to process gemini request for %s: %s\n", username, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(filepath.Join(rp, username))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkGemini(username string) error {
|
|
||||||
pgPath := filepath.Join("/home", username, "public_gemini")
|
|
||||||
if !pathExists(pgPath) {
|
|
||||||
return fmt.Errorf("public_gemini missing for %s", username)
|
|
||||||
}
|
|
||||||
|
|
||||||
geminiPath := filepath.Join(geminiHomeDocBase, username)
|
|
||||||
|
|
||||||
if !pathExists(geminiPath) {
|
|
||||||
err := os.Symlink(pgPath, geminiPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to link public_gemini for %s: %w", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body := fmt.Sprintf(`hi %s!
|
|
||||||
|
|
||||||
you requested a gemini space on the town. this space has been activated and anything you do in your public_gemini directory should now be reflected by the server.
|
|
||||||
|
|
||||||
if you did _not_ request this, please let an admin know.`, username)
|
|
||||||
return email.SendLocalEmail(username, "gemini", body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathExists(path string) bool {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
179
request/gitea.go
179
request/gitea.go
|
@ -1,179 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
email "git.tilde.town/tildetown/town/email"
|
|
||||||
)
|
|
||||||
|
|
||||||
const pwLetters = "!@#$%^&*() []{}:;,.<>/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"
|
|
||||||
|
|
||||||
func processGitea(rp string) error {
|
|
||||||
apiToken := os.Getenv("GITEA_TOKEN")
|
|
||||||
if apiToken == "" {
|
|
||||||
return errors.New("need GITEA_TOKEN")
|
|
||||||
}
|
|
||||||
|
|
||||||
gtPath := filepath.Join(requestPath, "gitea")
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(gtPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
usernames := []string{}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
usernames = append(usernames, file.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(usernames) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, username := range usernames {
|
|
||||||
exists, err := giteaUserExists(apiToken, username)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "unable to check for existing account for %s: %s\n", username, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
password, err := createGiteaUser(apiToken, username)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "unable to create account for %s: %s\n", username, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = sendGiteaEmail(username, password)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to send email to %s; reach out manually: %s\n", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(filepath.Join(gtPath, username))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createGiteaUser(apiToken, username string) (string, error) {
|
|
||||||
client := &http.Client{}
|
|
||||||
password := genGiteaPassword()
|
|
||||||
|
|
||||||
// TODO using local email sucks for obvious reasons but it'll have to do for now. ideally password
|
|
||||||
// resets can be set local to the server, so the thing to change is not the local email but the
|
|
||||||
// ability for gitea to send mail internally.
|
|
||||||
createPayload := struct {
|
|
||||||
Email string
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Login string `json:"login_name"`
|
|
||||||
MustChangePassword bool `json:"must_change_password"`
|
|
||||||
Password string
|
|
||||||
SendNotify bool `json:"send_notify"`
|
|
||||||
Username string
|
|
||||||
SourceId int `json:"source_id"`
|
|
||||||
}{
|
|
||||||
Email: fmt.Sprintf("%s@tilde.town", username),
|
|
||||||
FullName: username,
|
|
||||||
Login: username,
|
|
||||||
MustChangePassword: true,
|
|
||||||
Password: password,
|
|
||||||
SendNotify: false,
|
|
||||||
Username: username,
|
|
||||||
SourceId: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := json.Marshal(createPayload)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := giteaAPIReq(apiToken, "POST", "admin/users", bytes.NewBuffer(body))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 201 {
|
|
||||||
lol, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
fmt.Printf("DBG %#v\n", string(lol))
|
|
||||||
return "", fmt.Errorf("failed to create user for %s; error code %d", username, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return password, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func giteaUserExists(apiToken, username string) (bool, error) {
|
|
||||||
client := &http.Client{}
|
|
||||||
req, err := giteaAPIReq(apiToken, "GET", "users/"+username, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == 200 {
|
|
||||||
return true, nil
|
|
||||||
} else if resp.StatusCode == 404 {
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
return false, fmt.Errorf("unexpected response code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func giteaAPIReq(apiToken, method, path string, body *bytes.Buffer) (*http.Request, error) {
|
|
||||||
if body == nil {
|
|
||||||
body = bytes.NewBufferString("")
|
|
||||||
}
|
|
||||||
basePath := "https://git.tilde.town/api/v1/"
|
|
||||||
req, err := http.NewRequest(method, basePath+path, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", apiToken))
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendGiteaEmail(username, password string) error {
|
|
||||||
body := fmt.Sprintf(`hi %s!
|
|
||||||
|
|
||||||
you requested a git.tilde.town account and now you have one~
|
|
||||||
|
|
||||||
please log in with username %s and password %s
|
|
||||||
|
|
||||||
you'll be prompted to change the password.
|
|
||||||
|
|
||||||
if you did _not_ request this, please let an admin know.
|
|
||||||
`, username, username, password)
|
|
||||||
|
|
||||||
return email.SendLocalEmail(username, "gitea", body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genGiteaPassword() string {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
b := make([]byte, 20)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = pwLetters[rand.Intn(len(pwLetters))]
|
|
||||||
}
|
|
||||||
// Bootleg, but hack to ensure we meet complexity requirement
|
|
||||||
return string(b) + "!" + "1" + "A"
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
|
|
||||||
townUser "git.tilde.town/tildetown/town/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
const requestPath = "/town/requests"
|
|
||||||
|
|
||||||
func _main(args []string) error {
|
|
||||||
currentUser, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := townUser.IsAdmin(currentUser)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return errors.New("must be a town admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := []error{}
|
|
||||||
|
|
||||||
err = processGitea(requestPath)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = processGemini(requestPath)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
errMsg := "errors encountered during request processing: "
|
|
||||||
for _, e := range errs {
|
|
||||||
errMsg += e.Error() + " "
|
|
||||||
}
|
|
||||||
return fmt.Errorf("errors encountered during request processing: %s", errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
retcode := 0
|
|
||||||
|
|
||||||
err := _main(os.Args)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
retcode = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(retcode)
|
|
||||||
}
|
|
BIN
request/request
BIN
request/request
Binary file not shown.
Loading…
Reference in New Issue