Browse Source

added account creation, skeleton directories, and a tad of refactoring

bmallred 9 years ago
parent
commit
3f6f93ece3
5 changed files with 381 additions and 14 deletions
  1. 2 2
      README.md
  2. 186 0
      account.go
  3. 83 0
      account_test.go
  4. 94 12
      main.go
  5. 16 0
      main_test.go

+ 2 - 2
README.md

@ -4,7 +4,7 @@ loop
4 4
Basic Structure
5 5
===============
6 6
7
./users
7
./loop_users
8 8
-------
9 9
10 10
User name and hashed password security. Not the best but definitely simple.
@ -40,5 +40,5 @@ Environment Variables
40 40
41 41
    - LOOP_URL      (Default: localhost:6006)
42 42
    - LOOP_SALT     (Default: whatyoutalkingaboutwillus)
43
    - LOOP_BASE     (Default: .)
43
    - LOOP_DATA     (Default: .)
44 44

+ 186 - 0
account.go

@ -0,0 +1,186 @@
1
package main
2
3
import (
4
	"encoding/json"
5
	"errors"
6
	"io/ioutil"
7
	"log"
8
	"os"
9
	"path/filepath"
10
	"strings"
11
	"time"
12
)
13
14
func getAccounts() ([]Account, error) {
15
	file := UserFile
16
17
	// Check to see if the file exists
18
	if _, err := os.Stat(file); os.IsNotExist(err) {
19
		return []Account{}, nil
20
	}
21
22
	// Open the file
23
	fi, err := os.Open(file)
24
	if err != nil {
25
		return nil, err
26
	}
27
28
	// Read all of its contents
29
	raw, err := ioutil.ReadAll(fi)
30
	if len(raw) == 0 {
31
		return []Account{}, nil
32
	}
33
34
	// Translate the JSON to internal structures
35
	var accounts []Account
36
	err = json.Unmarshal(raw, &accounts)
37
	if err != nil {
38
		return nil, err
39
	}
40
	fi.Close()
41
42
	return accounts, nil
43
}
44
45
func (a *Account) Validate() (bool, error) {
46
	file := UserFile
47
48
	// Determine if the "users" file exists
49
	// Attempt to create the file if it does not
50
	if _, err := os.Stat(file); os.IsNotExist(err) {
51
		_, err = os.Create(file)
52
		if err != nil {
53
			return false, err
54
		}
55
	}
56
57
	// Get a list of current accounts
58
	accounts, err := getAccounts()
59
	if err != nil {
60
		return false, err
61
	}
62
63
	// Search if an account and passphrase match is found
64
	for _, x := range accounts {
65
		if x.Username == a.Username && x.Passphrase == a.Passphrase {
66
			return true, nil
67
		}
68
	}
69
70
	return false, nil
71
}
72
73
func (a *Account) Create() error {
74
	// Ensure there are no accounts of that name already
75
	valid, err := a.Validate()
76
	if err != nil {
77
		return err
78
	}
79
	if valid {
80
		return errors.New("Account already exists")
81
	}
82
83
	// Create directory structure
84
	baseDir := os.Getenv("LOOP_DATA")
85
	userDir := strings.ToLower(a.Username)
86
	s := string(filepath.Separator)
87
	paths := []string{"content", "blog", "calendar"}
88
	for _, p := range paths {
89
		err = os.MkdirAll(baseDir+s+userDir+s+p, 0755)
90
		if err != nil {
91
			return err
92
		}
93
	}
94
95
	// Add the account to all of the accounts
96
	accounts, err := getAccounts()
97
	if err != nil {
98
		return err
99
	}
100
	accounts = append(accounts, *a)
101
102
	// Create user entry in "users" file
103
	// Marshal the JSON
104
	marshalled, err := json.Marshal(accounts)
105
	if err != nil {
106
		return err
107
	}
108
109
	// Write to the file
110
	fi, err := os.OpenFile(UserFile, os.O_TRUNC|os.O_WRONLY, 0666)
111
	if err != nil {
112
		return err
113
	}
114
	_, err = fi.Write(marshalled)
115
	if err != nil {
116
		return err
117
	}
118
	fi.Close()
119
120
	return nil
121
}
122
123
func (a *Account) Delete() error {
124
	// Ensure there are no accounts of that name already
125
	valid, err := a.Validate()
126
	if err != nil {
127
		return err
128
	}
129
	if !valid {
130
		return errors.New("Account does not exist")
131
	}
132
133
	// Remove user entry in "users" file
134
	accounts, err := getAccounts()
135
	if err != nil {
136
		return err
137
	}
138
139
	for i, account := range accounts {
140
		if account.Username == a.Username {
141
			accounts = append(accounts[:i], accounts[i+1:]...)
142
			break
143
		}
144
	}
145
146
	marshalled, err := json.Marshal(accounts)
147
	if err != nil {
148
		return err
149
	}
150
	log.Printf(string(marshalled))
151
152
	// Write to the file
153
	fi, err := os.OpenFile(UserFile, os.O_TRUNC|os.O_WRONLY, 0666)
154
	if err != nil {
155
		return err
156
	}
157
	_, err = fi.Write(marshalled)
158
	if err != nil {
159
		return err
160
	}
161
	fi.Close()
162
163
	// Delete directory structure
164
	baseDir := os.Getenv("LOOP_DATA")
165
	userDir := strings.ToLower(a.Username)
166
	s := string(filepath.Separator)
167
	err = os.RemoveAll(baseDir + s + userDir)
168
	if err != nil {
169
		return err
170
	}
171
172
	return nil
173
}
174
175
func (a *Account) Add(content, filename string, file []byte) (string, error) {
176
	// TODO: Create new content directory
177
	// TODO: Check if filename already exists
178
	// TODO: Write file to content directory
179
	return "", nil
180
}
181
182
func (a *Account) Publish(title, content string, date time.Time) (string, error) {
183
	// TODO: Check if symlink already exists
184
	// TODO: Create symbolic link in "blog" directory referencing content directory
185
	return "", nil
186
}

+ 83 - 0
account_test.go

@ -0,0 +1,83 @@
1
package main
2
3
import (
4
	"os"
5
	"testing"
6
)
7
8
func TestGetAccounts(t *testing.T) {
9
	a, err := getAccounts()
10
	if err != nil || a == nil {
11
		t.Error(err)
12
	}
13
}
14
15
func TestCreate(t *testing.T) {
16
	a := Account{
17
		Username:   "guest",
18
		Passphrase: "guest",
19
	}
20
21
	ConfigureEnvironment()
22
	err := a.Create()
23
	if err != nil {
24
		t.Error(err)
25
	}
26
27
	valid, err := a.Validate()
28
	if err != nil {
29
		t.Error(err)
30
	}
31
	if !valid {
32
		t.Error("Failed to create account")
33
	}
34
}
35
36
func TestValidate(t *testing.T) {
37
	a := Account{
38
		Username:   "fake",
39
		Passphrase: "fake",
40
	}
41
42
	valid, err := a.Validate()
43
	if err != nil {
44
		t.Error(err)
45
	}
46
	if valid {
47
		t.Error("Fake account should not exist")
48
	}
49
}
50
51
func TestAdd(t *testing.T) {
52
}
53
54
func TestPublish(t *testing.T) {
55
}
56
57
func TestDelete(t *testing.T) {
58
	a := Account{
59
		Username:   "guest",
60
		Passphrase: "guest",
61
	}
62
63
	ConfigureEnvironment()
64
	err := a.Delete()
65
	if err != nil {
66
		t.Error(err)
67
	}
68
69
	valid, err := a.Validate()
70
	if err != nil {
71
		t.Error(err)
72
	}
73
	if valid {
74
		t.Error("Failed to delete account")
75
	}
76
}
77
78
func TestCleanup(t *testing.T) {
79
	err := os.Remove("loop_users")
80
	if err != nil {
81
		t.Error(err)
82
	}
83
}

+ 94 - 12
main.go

@ -1,17 +1,29 @@
1 1
package main
2 2
3 3
import (
4
	"crypto/aes"
5
	"crypto/cipher"
6
	"crypto/rand"
7
	"crypto/sha512"
8
	"encoding/base64"
9
	"errors"
10
	"fmt"
11
	"io"
4 12
	"log"
5 13
	"net/http"
6 14
	"os"
7
	"time"
15
	"strings"
16
)
17
18
const (
19
	UserFile = "loop_users"
8 20
)
9 21
10 22
var (
11 23
	EnvironmentVariables = map[string]string{
12 24
		"LOOP_URL":  "localhost:6006",
13 25
		"LOOP_SALT": "whatyoutalkingaboutwillus",
14
		"LOOP_BASE": ".",
26
		"LOOP_DATA": ".",
15 27
	}
16 28
)
17 29
@ -33,21 +45,91 @@ func main() {
33 45
	log.Fatal(http.ListenAndServe(address(), nil))
34 46
}
35 47
48
// General stuff
49
50
type Account struct {
51
	Username   string `json:username`
52
	Passphrase string `json:passphrase`
53
}
54
36 55
func address() string {
37 56
	return os.Getenv("LOOP_URL")
38 57
}
39
func validateCredentials(username, password, salt string) bool {
40
	return false
58
59
// Security stuff
60
61
func encodeBase64(data []byte) string {
62
	return base64.StdEncoding.EncodeToString(data)
63
}
64
func decodeBase64(data []byte) []byte {
65
	b, _ := base64.StdEncoding.DecodeString(string(data))
66
	return b
67
}
68
func salt() string {
69
	return os.Getenv("LOOP_SALT")
70
}
71
func getSizedKey(key string) string {
72
	// Get the correct key length
73
	l := len(key)
74
	if l < 16 {
75
		for i := 0; i < 16-l; i++ {
76
			key += "."
77
		}
78
	} else if l < 24 {
79
		for i := 0; i < 24-l; i++ {
80
			key += "."
81
		}
82
	} else if l < 32 {
83
		for i := 0; i < 32-l; i++ {
84
			key += "."
85
		}
86
	} else {
87
		key = key[:32]
88
	}
89
90
	return key
41 91
}
42
func createUser(username string) (string, error) {
43
	return "", nil
92
func encrypt(text, passphrase string) ([]byte, error) {
93
	key := []byte(getSizedKey(passphrase))
94
	block, err := aes.NewCipher(key)
95
	if err != nil {
96
		return nil, err
97
	}
98
	cipherText := make([]byte, aes.BlockSize+len(text))
99
	iv := cipherText[:aes.BlockSize]
100
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
101
		return nil, err
102
	}
103
	encrypter := cipher.NewCFBEncrypter(block, iv)
104
	encrypter.XORKeyStream(cipherText[aes.BlockSize:], []byte(text))
105
	return cipherText, nil
44 106
}
45
func createContent(username string) (string, error) {
46
	return "", nil
107
func decrypt(text []byte, passphrase string) ([]byte, error) {
108
	key := []byte(getSizedKey(passphrase))
109
	block, err := aes.NewCipher(key)
110
	if err != nil {
111
		return nil, err
112
	}
113
	if len(text) < aes.BlockSize {
114
		return nil, errors.New("Cipher text too short")
115
	}
116
	iv := text[:aes.BlockSize]
117
	data := text[aes.BlockSize:]
118
	decrypter := cipher.NewCFBDecrypter(block, iv)
119
	decrypter.XORKeyStream(data, data)
120
	return data, nil
47 121
}
48
func addContent(content, filename string, file []byte) (string, error) {
49
	return "", nil
122
func hash(username, passphrase, salt string) []byte {
123
	clearText := fmt.Sprintf(
124
		"%s%s-%s",
125
		salt,
126
		strings.ToLower(username),
127
		strings.ToLower(passphrase))
128
129
	sha := sha512.New()
130
	sha.Write([]byte(clearText))
131
	return sha.Sum(nil)
50 132
}
51
func publishContent(title, content string, date time.Time) (string, error) {
52
	return "", nil
133
func generatePassphrase(username, passphrase string) string {
134
	return encodeBase64(hash(username, passphrase, salt()))
53 135
}

+ 16 - 0
main_test.go

@ -0,0 +1,16 @@
1
package main
2
3
import (
4
	"os"
5
	"testing"
6
)
7
8
func TestAddress(t *testing.T) {
9
	expected := "127.0.0.1:8888"
10
	os.Setenv("LOOP_URL", expected)
11
	actual := os.Getenv("LOOP_URL")
12
13
	if actual != expected {
14
		t.FailNow()
15
	}
16
}