| @ -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(), | |||||
| }) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -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() | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,9 +1,18 @@ | |||||
| package Models | 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 { | type Friend struct { | ||||
| Base | 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"` | |||||
| } | } | ||||
| @ -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 | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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, | |||||
| }); | |||||
| } | |||||
| @ -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, | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } | |||||
| @ -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(), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } | |||||
| @ -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'), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } | |||||