Browse Source

Start adding routes for mobile interaction

Add data seeders for testing
pull/1/head
Tovi Jaeschke-Rogers 3 years ago
parent
commit
1a9f763112
28 changed files with 1182 additions and 63 deletions
  1. +0
    -1
      Backend/Api/Auth/Login.go
  2. +34
    -0
      Backend/Api/Auth/Logout.go
  3. +41
    -0
      Backend/Api/Friends/EncryptedFriendsList.go
  4. +59
    -0
      Backend/Api/Friends/FriendRequest.go
  5. +32
    -0
      Backend/Api/Friends/Friends.go
  6. +43
    -0
      Backend/Api/Messages/MessageThread.go
  7. +17
    -22
      Backend/Api/Routes.go
  8. +57
    -0
      Backend/Database/Friends.go
  9. +1
    -1
      Backend/Database/Init.go
  10. +52
    -0
      Backend/Database/Messages.go
  11. +67
    -0
      Backend/Database/Seeder/FriendSeeder.go
  12. +136
    -0
      Backend/Database/Seeder/MessageSeeder.go
  13. +50
    -0
      Backend/Database/Seeder/Seed.go
  14. +68
    -0
      Backend/Database/Seeder/UserSeeder.go
  15. +12
    -3
      Backend/Models/Friends.go
  16. +16
    -5
      Backend/Models/Messages.go
  17. +21
    -0
      Backend/Util/Bytes.go
  18. +51
    -0
      Backend/Util/UserHelper.go
  19. +1
    -1
      Backend/go.mod
  20. +15
    -0
      Backend/main.go
  21. +30
    -0
      mobile/lib/models/conversations.dart
  22. +5
    -0
      mobile/lib/utils/storage/encryption_keys.dart
  23. +0
    -2
      mobile/lib/views/authentication/login.dart
  24. +162
    -0
      mobile/lib/views/main/conversation_detail.dart
  25. +86
    -27
      mobile/lib/views/main/conversation_list.dart
  26. +60
    -0
      mobile/lib/views/main/conversation_list_item.dart
  27. +2
    -1
      mobile/lib/views/main/home.dart
  28. +64
    -0
      mobile/lib/views/main/profile.dart

+ 0
- 1
Backend/Api/Auth/Login.go View File

@ -48,7 +48,6 @@ func makeLoginResponse(w http.ResponseWriter, code int, message, pubKey, privKey
// Return updated json
w.WriteHeader(code)
w.Write(returnJson)
}
func Login(w http.ResponseWriter, r *http.Request) {


+ 34
- 0
Backend/Api/Auth/Logout.go View File

@ -0,0 +1,34 @@
package Auth
import (
"net/http"
"time"
)
func Logout(w http.ResponseWriter, r *http.Request) {
var (
c *http.Cookie
sessionToken string
err error
)
c, err = r.Cookie("session_token")
if err != nil {
if err == http.ErrNoCookie {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}
sessionToken = c.Value
delete(Sessions, sessionToken)
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Now(),
})
}

+ 41
- 0
Backend/Api/Friends/EncryptedFriendsList.go View File

@ -0,0 +1,41 @@
package Friends
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
func EncryptedFriendList(w http.ResponseWriter, r *http.Request) {
var (
userSession Auth.Session
friends []Models.Friend
returnJson []byte
err error
)
userSession, err = Auth.CheckCookie(r)
if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
friends, err = Database.GetFriendsByUserId(userSession.UserID)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
returnJson, err = json.MarshalIndent(friends, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}

+ 59
- 0
Backend/Api/Friends/FriendRequest.go View File

@ -0,0 +1,59 @@
package Friends
import (
"encoding/json"
"io/ioutil"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Util"
)
func FriendRequest(w http.ResponseWriter, r *http.Request) {
var (
user Models.User
requestBody []byte
requestJson map[string]interface{}
friendID []byte
friendRequest Models.Friend
err error
)
user, err = Util.GetUserById(w, r)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
json.Unmarshal(requestBody, &requestJson)
if requestJson["id"] == nil {
http.Error(w, "Invalid Data", http.StatusBadRequest)
return
}
friendID, err = Util.ToBytes(requestJson["id"])
if requestJson["id"] == nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
friendRequest = Models.Friend{
UserID: user.ID,
FriendID: friendID,
}
err = Database.CreateFriendRequest(&friendRequest)
if requestJson["id"] == nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

+ 32
- 0
Backend/Api/Friends/Friends.go View File

@ -0,0 +1,32 @@
package Friends
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Util"
)
func Friend(w http.ResponseWriter, r *http.Request) {
var (
userData Models.User
returnJson []byte
err error
)
userData, err = Util.GetUserById(w, r)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
returnJson, err = json.MarshalIndent(userData, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}

+ 43
- 0
Backend/Api/Messages/MessageThread.go View File

@ -0,0 +1,43 @@
package Messages
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
func MessageThread(w http.ResponseWriter, r *http.Request) {
var (
messages []Models.Message
urlVars map[string]string
threadID string
returnJson []byte
ok bool
err error
)
urlVars = mux.Vars(r)
threadID, ok = urlVars["threadID"]
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
messages, err = Database.GetMessagesByThreadId(threadID)
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
returnJson, err = json.MarshalIndent(messages, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}

+ 17
- 22
Backend/Api/Routes.go View File

@ -5,6 +5,8 @@ import (
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Friends"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Messages"
"github.com/gorilla/mux"
)
@ -12,8 +14,7 @@ import (
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf(
"%s %s %s, Content Length: %d",
r.RemoteAddr,
"%s %s, Content Length: %d",
r.Method,
r.RequestURI,
r.ContentLength,
@ -26,50 +27,44 @@ func loggingMiddleware(next http.Handler) http.Handler {
func authenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
//userSession Auth.Session
//err error
err error
)
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
/**
userSession, err = Auth.CheckCookie(r)
_, err = Auth.CheckCookie(r)
if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
log.Printf(
"Authenticated user: %s (%s)",
userSession.Email,
userSession.UserID,
)
next.ServeHTTP(w, r)
*/
})
}
func InitApiEndpoints(router *mux.Router) {
var (
api *mux.Router
adminApi *mux.Router
api *mux.Router
authApi *mux.Router
)
log.Println("Initializing API routes...")
api = router.PathPrefix("/api/v1/").Subrouter()
api.Use(loggingMiddleware)
// Define routes for authentication
api.HandleFunc("/signup", Auth.Signup).Methods("POST")
api.HandleFunc("/login", Auth.Login).Methods("POST")
// api.HandleFunc("/logout", Auth.Logout).Methods("GET")
api.HandleFunc("/logout", Auth.Logout).Methods("GET")
authApi = api.PathPrefix("/auth/").Subrouter()
authApi.Use(authenticationMiddleware)
adminApi = api.PathPrefix("/message/").Subrouter()
// Define routes for friends and friend requests
authApi.HandleFunc("/friends", Friends.EncryptedFriendList).Methods("GET")
authApi.HandleFunc("/friend/{userID}", Friends.Friend).Methods("GET")
authApi.HandleFunc("/friend/{userID}/request", Friends.FriendRequest).Methods("POST")
adminApi.Use(authenticationMiddleware)
// Define routes for messages
authApi.HandleFunc("/messages/{threadID}", Messages.MessageThread).Methods("GET")
}

+ 57
- 0
Backend/Database/Friends.go View File

@ -0,0 +1,57 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gofrs/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetFriendById(id string) (Models.Friend, error) {
var (
friend Models.Friend
err error
)
err = DB.Preload(clause.Associations).
First(&friend, "id = ?", id).
Error
return friend, err
}
func GetFriendsByUserId(userID string) ([]Models.Friend, error) {
var (
friends []Models.Friend
err error
)
err = DB.Model(Models.Friend{}).
Where("user_id = ?", userID).
Find(&friends).
Error
return friends, err
}
func CreateFriendRequest(friend *Models.Friend) error {
var (
err error
)
friend.ThreadID, err = uuid.NewV1()
if err != nil {
return err
}
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(friend).
Error
}
func DeleteFriend(friend *Models.Friend) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(friend).
Error
}

+ 1
- 1
Backend/Database/Init.go View File

@ -21,7 +21,7 @@ func GetModels() []interface{} {
&Models.User{},
&Models.Friend{},
&Models.MessageData{},
&Models.MessageKey{},
&Models.Message{},
}
}


+ 52
- 0
Backend/Database/Messages.go View File

@ -0,0 +1,52 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetMessageById(id string) (Models.Message, error) {
var (
message Models.Message
err error
)
err = DB.Preload(clause.Associations).
First(&message, "id = ?", id).
Error
return message, err
}
func GetMessagesByThreadId(id string) ([]Models.Message, error) {
var (
messages []Models.Message
err error
)
err = DB.Preload(clause.Associations).
Find(&messages, "thread_id = ?", id).
Error
return messages, err
}
func CreateMessage(message *Models.Message) error {
var (
err error
)
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(message).
Error
return err
}
func DeleteMessage(message *Models.Message) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(message).
Error
}

+ 67
- 0
Backend/Database/Seeder/FriendSeeder.go View File

@ -0,0 +1,67 @@
package Seeder
import (
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
func seedFriend(user, friendUser Models.User) error {
var (
friend Models.Friend
err error
)
friend = Models.Friend{
UserID: user.ID,
FriendID: encryptWithPublicKey(friendUser.ID.Bytes(), decodedPublicKey),
AcceptedAt: time.Now(),
}
err = Database.CreateFriendRequest(&friend)
if err != nil {
return err
}
friend = Models.Friend{
UserID: friendUser.ID,
FriendID: encryptWithPublicKey(user.ID.Bytes(), decodedPublicKey),
AcceptedAt: time.Now(),
}
return Database.CreateFriendRequest(&friend)
}
func SeedFriends() {
var (
primaryUser Models.User
secondaryUser Models.User
i int
err error
)
primaryUser, err = Database.GetUserByUsername("testUser")
if err != nil {
panic(err)
}
secondaryUser, err = Database.GetUserByUsername("testUser2")
if err != nil {
panic(err)
}
err = seedFriend(primaryUser, secondaryUser)
for i = 0; i <= 3; i++ {
secondaryUser, err = Database.GetUserByUsername(userNames[i])
if err != nil {
panic(err)
}
err = seedFriend(primaryUser, secondaryUser)
if err != nil {
panic(err)
}
}
}

+ 136
- 0
Backend/Database/Seeder/MessageSeeder.go View File

@ -0,0 +1,136 @@
package Seeder
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"encoding/pem"
"hash"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gofrs/uuid"
)
// EncryptWithPublicKey encrypts data with public key
func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
var (
hash hash.Hash
)
hash = sha512.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
if err != nil {
panic(err)
}
return ciphertext
}
func PKCS5Padding(ciphertext []byte, blockSize int, after int) []byte {
var (
padding int
padtext []byte
)
padding = (blockSize - len(ciphertext)%blockSize)
padtext = bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func seedMessage(primaryUser, secondaryUser Models.User, threadID uuid.UUID, i int) error {
var (
messageKey Models.Message
messageData Models.MessageData
block cipher.Block
mode cipher.BlockMode
pemBlock *pem.Block
plaintext string
ciphertext []byte
bKey []byte
bIV []byte
bPlaintext []byte
err error
)
plaintext = "Test Message"
bKey = make([]byte, 32)
_, err = rand.Read(bKey)
if err != nil {
panic(err)
}
bIV = make([]byte, 16)
_, err = rand.Read(bIV)
if err != nil {
panic(err)
}
bPlaintext = PKCS5Padding([]byte(plaintext), aes.BlockSize, len(plaintext))
pemBlock = &pem.Block{
Type: "AES KEY",
Bytes: bKey,
}
block, err = aes.NewCipher(bKey)
if err != nil {
panic(err)
}
ciphertext = make([]byte, len(bPlaintext))
mode = cipher.NewCBCEncrypter(block, bIV)
mode.CryptBlocks(ciphertext, bPlaintext)
messageData = Models.MessageData{
Data: ciphertext,
}
messageKey = Models.Message{
UserID: primaryUser.ID,
MessageData: messageData,
MessageType: "sender",
RelationalUserId: encryptWithPublicKey(secondaryUser.ID.Bytes(), decodedPublicKey),
SymmetricKey: string(pem.EncodeToMemory(pemBlock)),
}
return Database.CreateMessage(&messageKey)
}
func SeedMessages() {
var (
primaryUser Models.User
secondaryUser Models.User
threadID uuid.UUID
i int
err error
)
primaryUser, err = Database.GetUserByUsername("testUser")
if err != nil {
panic(err)
}
secondaryUser, err = Database.GetUserByUsername("testUser2")
if err != nil {
panic(err)
}
threadID, err = uuid.NewV4()
if err != nil {
panic(err)
}
for i = 0; i <= 20; i++ {
err = seedMessage(primaryUser, secondaryUser, threadID, i)
if err != nil {
panic(err)
}
}
}

+ 50
- 0
Backend/Database/Seeder/Seed.go View File

@ -0,0 +1,50 @@
package Seeder
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log"
)
const (
// Encrypted with "password"
encryptedPrivateKey string = `
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
`
publicKey string = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAidHsO9H0fMd6cu9dQiOwu/qtaDq+lG59kudoHGx8WZhucpOSp2df95UzjnXmMw1fz0Mx5fjgBaunG7u1MVBy+7IdYwXp+iz3xZulVy1Yv3e+GMzgQhAuxhz/8wsHkFkUTweDZCrCZPhkTXlCrxDIuKykQ0el+RnpSjyyljOsAWAdTmrz0a2Nh9FOmF1v49pWy3Z3aJG58xl1dmFkpXjT3m36GB4Z30iR1uOMnNdrtfwIfLQAc7nmle2LxCHeEMYzlA9y6JChm5kGI6FmglSKYTxvDm40jA5mYyDCPADeCodbIw4Mtm0nwrM3QqKWj+jlaL8BY7/jjaosmz7VK2do4wIDAQAB
-----END PUBLIC KEY-----
`
)
var (
decodedPublicKey *rsa.PublicKey
)
func Seed() {
var (
block *pem.Block
decKey any
err error
)
// TODO: Fix this parsing
block, _ = pem.Decode([]byte(publicKey))
decKey, err = x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
decodedPublicKey = decKey.(*rsa.PublicKey)
log.Println("Seeding users...")
SeedUsers()
log.Println("Seeding friend connections...")
SeedFriends()
log.Println("Seeding messages...")
SeedMessages()
}

+ 68
- 0
Backend/Database/Seeder/UserSeeder.go View File

@ -0,0 +1,68 @@
package Seeder
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
var userNames = []string{
"assuredcoot",
"quotesteeve",
"blueberriessiemens",
"eliteexaggerate",
"twotrice",
"moderagged",
"duleelderly",
"stringdetailed",
"nodesanymore",
"sacredpolitical",
"pajamasenergy",
}
func createUser(username string) (Models.User, error) {
var (
userData Models.User
password string
err error
)
password, err = Auth.HashPassword("password")
if err != nil {
return Models.User{}, err
}
userData = Models.User{
Username: username,
Password: password,
AsymmetricPrivateKey: encryptedPrivateKey,
AsymmetricPublicKey: publicKey,
}
err = Database.CreateUser(&userData)
return userData, err
}
func SeedUsers() {
var (
i int
err error
)
// Seed users used for conversation seeding
_, err = createUser("testUser")
if err != nil {
panic(err)
}
_, err = createUser("testUser2")
if err != nil {
panic(err)
}
for i = 0; i <= 10; i++ {
_, err = createUser(userNames[i])
if err != nil {
panic(err)
}
}
}

+ 12
- 3
Backend/Models/Friends.go View File

@ -1,9 +1,18 @@
package Models
import "github.com/gofrs/uuid"
import (
"time"
"github.com/gofrs/uuid"
)
// Set with User being the requestee, and FriendID being the requester
type Friend struct {
Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
FriendId string `gorm:"column:friend_id;not null" json:"friend_id"` // Stored encrypted
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
User User `json:"user"`
FriendID []byte `gorm:"not null" json:"friend_id"` // Stored encrypted
ThreadID uuid.UUID `gorm:"type:uuid;column:thread_id;not null;" json:"thread_id"`
Thread []Message `gorm:"foreignKey:thread_id" json:"-"`
AcceptedAt time.Time `json:"accepted_at"`
}

+ 16
- 5
Backend/Models/Messages.go View File

@ -2,14 +2,25 @@ package Models
import "github.com/gofrs/uuid"
const (
MessageTypeSender = "sender"
MessageTypeReceiver = "reciever"
)
type MessageData struct {
Base
Data string `gorm:"not null" json:"data"` // Stored encrypted
Data []byte `gorm:"not null" json:"data"` // Stored encrypted
}
type MessageKey struct {
// TODO: Rename this to something better
type Message struct {
Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
MessageDataID uuid.UUID `gorm:"type:uuid;column:message_data_id;not null;" json:"message_data_id"`
SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted
ThreadID uuid.UUID `gorm:"not null" json:"thread_id"`
UserID uuid.UUID `json:"-"`
User User `json:"user"`
MessageDataID uuid.UUID `json:"-"`
MessageData MessageData `json:"message_data"`
MessageType string `gorm:"not null" json:"message_type"` // sender / reciever
RelationalUserId []byte `gorm:"not null" json:"relational_user_id"` // Stored encrypted. UserID for the user this message is in relation to
SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted
}

+ 21
- 0
Backend/Util/Bytes.go View File

@ -0,0 +1,21 @@
package Util
import (
"bytes"
"encoding/gob"
)
func ToBytes(key interface{}) ([]byte, error) {
var (
buf bytes.Buffer
enc *gob.Encoder
err error
)
enc = gob.NewEncoder(&buf)
err = enc.Encode(key)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

+ 51
- 0
Backend/Util/UserHelper.go View File

@ -0,0 +1,51 @@
package Util
import (
"errors"
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
func GetUserId(r *http.Request) (string, error) {
var (
urlVars map[string]string
id string
ok bool
)
urlVars = mux.Vars(r)
id, ok = urlVars["userID"]
if !ok {
return id, errors.New("Could not get id")
}
return id, nil
}
func GetUserById(w http.ResponseWriter, r *http.Request) (Models.User, error) {
var (
postData Models.User
id string
err error
)
id, err = GetUserId(r)
if err != nil {
log.Printf("Error encountered getting id\n")
http.Error(w, "Error", http.StatusInternalServerError)
return postData, err
}
postData, err = Database.GetUserById(id)
if err != nil {
log.Printf("Could not find user with id %s\n", id)
http.Error(w, "Error", http.StatusInternalServerError)
return postData, err
}
return postData, nil
}

+ 1
- 1
Backend/go.mod View File

@ -6,6 +6,7 @@ require (
github.com/Kangaroux/go-map-schema v0.6.1
github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/mux v1.8.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
gorm.io/driver/postgres v1.3.4
gorm.io/gorm v1.23.4
)
@ -21,6 +22,5 @@ require (
github.com/jackc/pgx/v4 v4.15.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/text v0.3.7 // indirect
)

+ 15
- 0
Backend/main.go View File

@ -1,16 +1,26 @@
package main
import (
"flag"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database/Seeder"
"github.com/gorilla/mux"
)
var (
seed bool
)
func init() {
Database.Init()
flag.BoolVar(&seed, "seed", false, "Seed database for development")
flag.Parse()
}
func main() {
@ -18,6 +28,11 @@ func main() {
router *mux.Router
)
if seed {
Seeder.Seed()
return
}
router = mux.NewRouter()
Api.InitApiEndpoints(router)


+ 30
- 0
mobile/lib/models/conversations.dart View File

@ -0,0 +1,30 @@
const messageTypeSender = 'sender';
const messageTypeReceiver = 'receiver';
class Message {
String id;
String conversationId;
String symmetricKey;
String data;
String messageType;
String? decryptedData;
Message({
required this.id,
required this.conversationId,
required this.symmetricKey,
required this.data,
required this.messageType,
this.decryptedData,
});
}
class Conversation {
String id;
String friendId;
String recentMessageId;
Conversation({
required this.id,
required this.friendId,
required this.recentMessageId,
});
}

+ 5
- 0
mobile/lib/utils/storage/encryption_keys.dart View File

@ -11,6 +11,11 @@ void setPrivateKey(RSAPrivateKey key) async {
prefs.setString(rsaPrivateKeyName, keyPem);
}
void unsetPrivateKey() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(rsaPrivateKeyName);
}
Future<RSAPrivateKey> getPrivateKey() async {
final prefs = await SharedPreferences.getInstance();
String? keyPem = prefs.getString(rsaPrivateKeyName);


+ 0
- 2
mobile/lib/views/authentication/login.dart View File

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '/views/main/conversation_list.dart';
import '/utils/encryption/rsa_key_helper.dart';
import '/utils/encryption/aes_helper.dart';
import '/utils/storage/encryption_keys.dart';
@ -73,7 +72,6 @@ class Login extends StatelessWidget {
//`true` if you want Flutter to automatically add Back Button when needed,
//or `false` if you want to force your own back button every where
leading: IconButton(icon: const Icon(Icons.arrow_back),
//onPressed:() => Navigator.pop(context, false),
onPressed:() => {
Navigator.pop(context)
}


+ 162
- 0
mobile/lib/views/main/conversation_detail.dart View File

@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import '/models/conversations.dart';
class ConversationDetail extends StatefulWidget{
const ConversationDetail({Key? key}) : super(key: key);
@override
_ConversationDetailState createState() => _ConversationDetailState();
}
class _ConversationDetailState extends State<ConversationDetail> {
Conversation conversation = Conversation(
id: '777',
friendId: 'abc',
recentMessageId: '111',
);
List<Message> messages = [
Message(
id: '444',
conversationId: '777',
symmetricKey: '',
data: 'This is a message',
messageType: messageTypeSender,
),
Message(
id: '444',
conversationId: '777',
symmetricKey: '',
data: 'This is a message',
messageType: messageTypeReceiver,
),
Message(
id: '444',
conversationId: '777',
symmetricKey: '',
data: 'This is a message',
messageType: messageTypeSender
),
Message(
id: '444',
conversationId: '777',
symmetricKey: '',
data: 'This is a message',
messageType: messageTypeReceiver,
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
flexibleSpace: SafeArea(
child: Container(
padding: const EdgeInsets.only(right: 16),
child: Row(
children: <Widget>[
IconButton(
onPressed: (){
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back,color: Colors.black,),
),
const SizedBox(width: 2,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text("Kriss Benwat",style: TextStyle( fontSize: 16 ,fontWeight: FontWeight.w600),),
],
),
),
const Icon(Icons.settings,color: Colors.black54,),
],
),
),
),
),
body: Stack(
children: <Widget>[
ListView.builder(
itemCount: messages.length,
shrinkWrap: true,
padding: const EdgeInsets.only(top: 10,bottom: 10),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.only(left: 14,right: 14,top: 10,bottom: 10),
child: Align(
alignment: (messages[index].messageType == messageTypeReceiver ? Alignment.topLeft:Alignment.topRight),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: (messages[index].messageType == messageTypeReceiver ? Colors.grey.shade200:Colors.blue[200]),
),
padding: const EdgeInsets.all(16),
child: Text(messages[index].data, style: const TextStyle(fontSize: 15)),
),
),
);
},
),
Align(
alignment: Alignment.bottomLeft,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 200.0,
),
child: Container(
padding: const EdgeInsets.only(left: 10,bottom: 10,top: 10),
// height: 60,
width: double.infinity,
color: Colors.white,
child: Row(
children: <Widget>[
GestureDetector(
onTap: (){
},
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(30),
),
child: const Icon(Icons.add, color: Colors.white, size: 20, ),
),
),
const SizedBox(width: 15,),
const Expanded(
child: TextField(
decoration: InputDecoration(
hintText: "Write message...",
hintStyle: TextStyle(color: Colors.black54),
border: InputBorder.none,
),
maxLines: null,
),
),
const SizedBox(width: 15,),
FloatingActionButton(
onPressed: () {
},
child: const Icon(Icons.send,color: Colors.white,size: 18,),
backgroundColor: Colors.blue,
elevation: 0,
),
],
),
),
),
),
],
),
);
}
}

+ 86
- 27
mobile/lib/views/main/conversation_list.dart View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '/models/conversations.dart';
import '/views/main/conversation_list_item.dart';
class ConversationList extends StatefulWidget {
const ConversationList({Key? key}) : super(key: key);
@ -9,35 +10,41 @@ class ConversationList extends StatefulWidget {
}
class _ConversationListState extends State<ConversationList> {
final _suggestions = <String>[];
final _biggerFont = const TextStyle(fontSize: 18);
List<Message> messages = [
Message(
id: '123',
conversationId: 'xyz',
data: '',
symmetricKey: '',
messageType: 'reciever',
),
];
List<Conversation> friends = [
Conversation(
id: 'xyz',
friendId: 'abc',
recentMessageId: '123',
),
];
Widget list() {
if (_suggestions.isEmpty) {
if (friends.isEmpty) {
return const Center(
child: Text('No Conversations'),
);
}
return ListView.builder(
itemCount: _suggestions.length,
padding: const EdgeInsets.all(16.0),
itemBuilder: /*1*/ (context, i) {
//if (i >= _suggestions.length) {
// TODO: Check for more conversations here. Remove the itemCount to use this section
//_suggestions.addAll(generateWordPairs().take(10)); /*4*/
//}
return Column(
children: [
ListTile(
title: Text(
_suggestions[i],
style: _biggerFont,
),
),
const Divider(),
]
itemCount: friends.length,
shrinkWrap: true,
padding: const EdgeInsets.only(top: 16),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, i) {
return ConversationListItem(
id: friends[i].id,
username: 'Test',
);
},
);
@ -46,11 +53,63 @@ class _ConversationListState extends State<ConversationList> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Envelope'),
),
body: list(),
);
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 16,right: 16,top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text("Conversations",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),),
Container(
padding: const EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2),
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.pink[50],
),
child: Row(
children: const <Widget>[
Icon(Icons.add,color: Colors.pink,size: 20,),
SizedBox(width: 2,),
Text("Add",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),),
],
),
)
],
),
),
),
Padding(
padding: const EdgeInsets.only(top: 16,left: 16,right: 16),
child: TextField(
decoration: InputDecoration(
hintText: "Search...",
hintStyle: TextStyle(color: Colors.grey.shade600),
prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: 20,),
filled: true,
fillColor: Colors.grey.shade100,
contentPadding: const EdgeInsets.all(8),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(
color: Colors.grey.shade100
)
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 16,left: 16,right: 16),
child: list(),
),
],
),
),
);
}
}

+ 60
- 0
mobile/lib/views/main/conversation_list_item.dart View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import '/views/main/conversation_detail.dart';
class ConversationListItem extends StatefulWidget{
final String id;
final String username;
const ConversationListItem({
Key? key,
required this.id,
required this.username,
}) : super(key: key);
@override
_ConversationListItemState createState() => _ConversationListItemState();
}
class _ConversationListItemState extends State<ConversationListItem> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context){
return ConversationDetail();
}));
},
child: Container(
padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
// CircleAvatar(
// backgroundImage: NetworkImage(widget.imageUrl),
// maxRadius: 30,
// ),
//const SizedBox(width: 16),
Expanded(
child: Container(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget.username, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 6),
//Text(widget.messageText,style: TextStyle(fontSize: 13,color: Colors.grey.shade600, fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),),
const Divider(),
],
),
),
),
],
),
),
],
),
),
);
}
}

+ 2
- 1
mobile/lib/views/main/home.dart View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '/views/main/conversation_list.dart';
import '/views/main/friend_list.dart';
import '/views/main/profile.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@ -28,7 +29,7 @@ class _HomeState extends State<Home> {
static const List<Widget> _widgetOptions = <Widget>[
ConversationList(),
FriendList(),
Text('Not Implemented'),
Profile(),
];
void _onItemTapped(int index) {


+ 64
- 0
mobile/lib/views/main/profile.dart View File

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '/utils/storage/encryption_keys.dart';
class Profile extends StatefulWidget {
const Profile({Key? key}) : super(key: key);
@override
State<Profile> createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 16,right: 16,top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text("Profile",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),),
Container(
padding: const EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2),
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.pink[50],
),
child: GestureDetector(
onTap: () async {
final preferences = await SharedPreferences.getInstance();
preferences.setBool('islogin', false);
preferences.remove(rsaPrivateKeyName);
Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
},
child: Row(
children: const <Widget>[
Icon(Icons.logout, color: Colors.pink, size: 20,),
SizedBox(width: 2,),
Text("Logout",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),),
],
),
),
)
],
),
),
),
const Padding(
padding: EdgeInsets.only(top: 16,left: 16,right: 16),
child: Text('Test'),
),
],
),
),
);
}
}

Loading…
Cancel
Save