]> git.feebdaed.xyz Git - lgtm-rsvp.git/commitdiff
add authentication, session check
authorseantywork <seantywork@gmail.com>
Sat, 28 Dec 2024 09:32:59 +0000 (09:32 +0000)
committerseantywork <seantywork@gmail.com>
Sat, 28 Dec 2024 09:32:59 +0000 (09:32 +0000)
18 files changed:
go.mod
go.sum
init.sql
main_test_.go
pkg/auth/auth.go
pkg/auth/auth_idpw.go [new file with mode: 0644]
pkg/auth/auth_oauth2.go [new file with mode: 0644]
pkg/auth/auth_session.go [new file with mode: 0644]
pkg/auth/auth_verify.go [new file with mode: 0644]
pkg/db/db.go
pkg/db/db_admin.go [new file with mode: 0644]
pkg/db/db_media.go [new file with mode: 0644]
pkg/db/db_story.go [new file with mode: 0644]
pkg/server/api/api.go
pkg/server/server.go
pkg/server/server_view.go
start.sh
start_mkdb.sh [new file with mode: 0755]

diff --git a/go.mod b/go.mod
index e2e0e3a8336550d169a07890c0c08a51dcae2e85..41e97081c61cbd23560b4c31301fa2bf39759922 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,13 @@ go 1.23.2
 require (
        github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0
        github.com/gin-gonic/gin v1.10.0
+       github.com/mattn/go-sqlite3 v1.14.24
+       golang.org/x/oauth2 v0.24.0
+       gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
+       cloud.google.com/go/compute/metadata v0.3.0 // indirect
        github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
        github.com/bytedance/sonic v1.11.6 // indirect
        github.com/bytedance/sonic/loader v0.1.1 // indirect
@@ -27,7 +31,6 @@ require (
        github.com/klauspost/cpuid/v2 v2.2.7 // indirect
        github.com/leodido/go-urn v1.4.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
-       github.com/mattn/go-sqlite3 v1.14.24 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@@ -39,5 +42,4 @@ require (
        golang.org/x/sys v0.20.0 // indirect
        golang.org/x/text v0.15.0 // indirect
        google.golang.org/protobuf v1.34.1 // indirect
-       gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 83ab16e6e1b586421f3e446299633d1a21355082..4e790e82a9cab13d3d4acb4787033f01d2c51209 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
 github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
@@ -31,8 +33,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
 github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -89,14 +91,14 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
 google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
index 9eb2278c8d2d90887ba3367873b22d00095045d8..704dd138a3220ec3d0ed2b8fcdea511c8b547e9b 100644 (file)
--- a/init.sql
+++ b/init.sql
@@ -1,6 +1,7 @@
 CREATE TABLE admin (
     admin_id INTEGER PRIMARY KEY,
     id TEXT,
+    session_id TEXT,
     pw TEXT
 );
 CREATE TABLE story (
@@ -8,12 +9,7 @@ CREATE TABLE story (
     id TEXT,
     title TEXT,
     date_marked TEXT
-    primary_media_id TEXT,
+    primary_media_name TEXT,
     content TEXT
 );
-CREATE TABLE media (
-    media_id INTEGER PRIMARY KEY,
-    id TEXT,
-    kind TEXT,
-    content BLOB
-)
\ No newline at end of file
+
index f53713d97559f90e1bd96961b9cc53819af99bba..2a264ff2de94dd3a57c5c1f961987eef1a0d3d00 100644 (file)
@@ -30,7 +30,13 @@ func test(tc int) error {
 
 func test_db() error {
 
-       err := pkgdb.OpenDB(pkgglob.G_CONF.Db.Addr, pkgglob.G_CONF.Db.InitFile)
+       err := pkgdb.OpenDB(pkgglob.G_CONF.Db.Addr)
+
+       if err != nil {
+               return err
+       }
+
+       err = pkgdb.Init(pkgglob.G_CONF.Db.InitFile)
 
        if err != nil {
                return err
index 5e49c6681a94710b08a8112fd65d960538257927..b1e88abb0917aa56572244cbbabdad31950373dd 100644 (file)
@@ -2,7 +2,8 @@ package auth
 
 import (
        "fmt"
-       "os"
+
+       pkgdb "our-wedding-rsvp/pkg/db"
 
        "github.com/gin-gonic/contrib/sessions"
        "github.com/gin-gonic/gin"
@@ -28,168 +29,14 @@ func GenerateStateAuthCookie(c *gin.Context) string {
        return state
 }
 
-func RegisterAdmins(admins map[string]string) error {
-
-       err := os.RemoveAll("./data/admin")
-
-       if err != nil {
-               return fmt.Errorf("failed to remove data/admin")
-       }
+func RegisterAdmin(id string, pw string) error {
 
-       err = os.MkdirAll("./data/admin", 0755)
+       err := pkgdb.UpsertAdmin(id, pw)
 
        if err != nil {
-
-               return fmt.Errorf("failed to create data/admin")
-       }
-
-       for k, v := range admins {
-
-               ADMINS[k] = v
-
-               name := "./data/admin/" + k + ".json"
-
-               err := os.WriteFile(name, []byte("{}"), 0644)
-
-               if err != nil {
-
-                       return fmt.Errorf("failed to create data/admin: %s: %s", k, err.Error())
-               }
-
+               return fmt.Errorf("failed to register admin")
        }
 
        return nil
 
 }
-
-/*
-
-func OauthGoogleLogin(c *gin.Context) {
-
-       my_key, my_type, _ := WhoAmI(c)
-
-       if my_key != "" && my_type != "" {
-
-               fmt.Printf("oauth login: already logged in\n")
-
-               c.JSON(http.StatusBadRequest, com.SERVER_RE{Status: "error", Reply: "already logged in"})
-
-               return
-
-       }
-
-       if !USE_OAUTH2 {
-
-               c.Redirect(302, "/signin/idiot")
-
-               return
-       }
-
-       oauth_state := GenerateStateAuthCookie(c)
-
-       u := GoogleOauthConfig.AuthCodeURL(oauth_state)
-
-       c.Redirect(302, u)
-
-}
-
-func OauthGoogleCallback(c *gin.Context) {
-
-       my_key, my_type, _ := WhoAmI(c)
-
-       if my_key != "" && my_type != "" {
-
-               fmt.Printf("oauth callback: already logged in\n")
-
-               c.JSON(http.StatusBadRequest, com.SERVER_RE{Status: "error", Reply: "already logged in"})
-
-               return
-
-       }
-
-       session := sessions.Default(c)
-
-       var session_id string
-
-       v := session.Get("SOLIAGAIN")
-
-       if v == nil {
-               fmt.Printf("access auth failed: %s\n", "session id not found")
-               return
-       } else {
-               session_id = v.(string)
-       }
-
-       state := c.Request.FormValue("state")
-
-       if state == "" {
-               fmt.Printf("access auth failed: %s\n", "form state not found")
-               return
-       }
-
-       if state != session_id {
-               fmt.Printf("access auth failed: %s\n", "value not matching")
-               c.Redirect(302, "/signin")
-               return
-       }
-
-       data, err := GetUserDataFromGoogle(c.Request.FormValue("code"))
-       if err != nil {
-               fmt.Printf("access auth failed: %s\n", err.Error())
-               c.Redirect(302, "/signin")
-               return
-       }
-
-       var oauth_struct OAuthStruct
-
-       err = json.Unmarshal(data, &oauth_struct)
-
-       if err != nil {
-               fmt.Printf("access auth failed: %s\n", err.Error())
-               c.Redirect(302, "/signin")
-               return
-       }
-
-       if !oauth_struct.VERIFIED_EMAIL {
-               fmt.Printf("access auth failed: %s\n", err.Error())
-               c.Redirect(302, "/signin")
-               return
-       }
-
-       if err != nil {
-               fmt.Printf("access auth failed: %s\n", err.Error())
-               c.Redirect(302, "/signin")
-               return
-       }
-
-       as, err := dbquery.GetByIdFromAdmin(oauth_struct.EMAIL)
-
-       if as == nil {
-
-               fmt.Printf("access auth failed: %s", err.Error())
-
-               c.Redirect(302, "/signin")
-
-               return
-
-       }
-
-       err = dbquery.MakeSessionForAdmin(session_id, oauth_struct.EMAIL)
-
-       if err != nil {
-
-               fmt.Printf("make session failed for admin: %s", err.Error())
-
-               c.Redirect(302, "/signin")
-
-               return
-
-       }
-
-       fmt.Println("oauth sign in success")
-
-       c.Redirect(302, "/")
-
-}
-
-*/
diff --git a/pkg/auth/auth_idpw.go b/pkg/auth/auth_idpw.go
new file mode 100644 (file)
index 0000000..8832b06
--- /dev/null
@@ -0,0 +1 @@
+package auth
diff --git a/pkg/auth/auth_oauth2.go b/pkg/auth/auth_oauth2.go
new file mode 100644 (file)
index 0000000..a97fc93
--- /dev/null
@@ -0,0 +1,111 @@
+package auth
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "io"
+       "net/http"
+       "os"
+
+       "golang.org/x/oauth2"
+       "golang.org/x/oauth2/google"
+)
+
+type OAuthJSON struct {
+       Web struct {
+               ClientID                string   `json:"client_id"`
+               ProjectID               string   `json:"project_id"`
+               AuthURI                 string   `json:"auth_uri"`
+               TokenURI                string   `json:"token_uri"`
+               AuthProviderX509CertURL string   `json:"auth_provider_x509_cert_url"`
+               ClientSecret            string   `json:"client_secret"`
+               RedirectUris            []string `json:"redirect_uris"`
+       } `json:"web"`
+}
+
+const OauthGoogleUrlAPI = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
+
+type OAuthStruct struct {
+       ID             string `json:"id"`
+       EMAIL          string `json:"email"`
+       VERIFIED_EMAIL bool   `json:"verified_email"`
+       PICTURE        string `json:"picture"`
+}
+
+var OAUTH_JSON OAuthJSON
+
+var GoogleOauthConfig *oauth2.Config
+
+func InitAuth() {
+
+       if !USE_OAUTH2 {
+               return
+       }
+       OAUTH_JSON = GetOAuthJSON()
+
+       GoogleOauthConfig = GenerateGoogleOauthConfig()
+
+}
+
+func GetOAuthJSON() OAuthJSON {
+
+       var cj OAuthJSON
+
+       file_byte, err := os.ReadFile("oauth.json")
+
+       if err != nil {
+               panic(err)
+       }
+
+       err = json.Unmarshal(file_byte, &cj)
+
+       if err != nil {
+               panic(err)
+       }
+
+       return cj
+
+}
+
+func GenerateGoogleOauthConfig() *oauth2.Config {
+
+       google_oauth_config := &oauth2.Config{
+               ClientID:     OAUTH_JSON.Web.ClientID,
+               ClientSecret: OAUTH_JSON.Web.ClientSecret,
+               Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email"},
+               Endpoint:     google.Endpoint,
+       }
+
+       if DEBUG {
+
+               google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[0]
+
+       } else {
+
+               google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[1]
+       }
+
+       fmt.Println(google_oauth_config.RedirectURL)
+
+       return google_oauth_config
+
+}
+
+func GetUserDataFromGoogle(code string) ([]byte, error) {
+
+       token, err := GoogleOauthConfig.Exchange(context.Background(), code)
+       if err != nil {
+               return nil, fmt.Errorf("code exchange wrong: %s", err.Error())
+       }
+       response, err := http.Get(OauthGoogleUrlAPI + token.AccessToken)
+       if err != nil {
+               return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+       }
+       defer response.Body.Close()
+       contents, err := io.ReadAll(response.Body)
+       if err != nil {
+               return nil, fmt.Errorf("failed read response: %s", err.Error())
+       }
+       return contents, nil
+}
diff --git a/pkg/auth/auth_session.go b/pkg/auth/auth_session.go
new file mode 100644 (file)
index 0000000..76728d6
--- /dev/null
@@ -0,0 +1,44 @@
+package auth
+
+import (
+       "log"
+       pkgdb "our-wedding-rsvp/pkg/db"
+
+       "github.com/gin-gonic/contrib/sessions"
+       "github.com/gin-gonic/gin"
+)
+
+func Is0(c *gin.Context) bool {
+
+       session := sessions.Default(c)
+
+       var session_id string
+
+       v := session.Get("RSVP")
+
+       if v == nil {
+               return false
+
+       } else {
+
+               session_id = v.(string)
+
+               if session_id == "" {
+                       return false
+               }
+
+       }
+
+       s, err := pkgdb.GetAdminBySessionId(session_id)
+
+       if err != nil {
+               log.Printf("is0: err: %v\n", err)
+               return false
+       }
+
+       if s == nil {
+               return false
+       }
+
+       return true
+}
diff --git a/pkg/auth/auth_verify.go b/pkg/auth/auth_verify.go
new file mode 100644 (file)
index 0000000..07710fc
--- /dev/null
@@ -0,0 +1,47 @@
+package auth
+
+import (
+       "strings"
+       "unicode"
+)
+
+func VerifyMediaKey(mediakey string) bool {
+
+       mklist := strings.SplitN(mediakey, ".", 2)
+
+       for _, c := range mklist[0] {
+
+               if unicode.IsLetter(c) {
+
+                       continue
+
+               } else if unicode.IsDigit(c) {
+
+                       continue
+
+               } else {
+
+                       return false
+               }
+
+       }
+
+       for _, c := range mklist[1] {
+
+               if unicode.IsLetter(c) {
+
+                       continue
+
+               } else if unicode.IsDigit(c) {
+
+                       continue
+
+               } else {
+
+                       return false
+               }
+
+       }
+
+       return true
+}
index d42472ee34b0131879c9edb45b169df0075030fe..7afabcb84c39ecad3dc1d4d141119f86b782ef77 100644 (file)
@@ -3,72 +3,196 @@ package db
 import (
        "database/sql"
        "fmt"
+       "log"
+       "os"
 
        _ "github.com/mattn/go-sqlite3"
 )
 
 var _db *sql.DB
 
-func OpenDB(addr string, initfile string) error {
+var initiated bool = false
 
-       db, err := sql.Open("sqlite3", addr)
+type SqliteMaster struct {
+       TblName string
+}
+
+func query(query string, args []any) (*sql.Rows, error) {
+
+       var empty_row *sql.Rows
+
+       results, err := _db.Query(query, args[0:]...)
 
        if err != nil {
 
-               return err
+               return empty_row, fmt.Errorf("db query: %s", err.Error())
+
        }
 
-       _db = db
+       return results, err
 
-       err = runInitSql(initfile)
+}
+
+func exec(query string, args []any) error {
+
+       result, err := _db.Exec(query, args[0:]...)
 
        if err != nil {
+               return fmt.Errorf("db exec: %s", err.Error())
+       }
 
-               _db.Close()
+       _, err = result.RowsAffected()
+
+       if err != nil {
 
-               return fmt.Errorf("failed to run init sql: %v", err)
+               return fmt.Errorf("db exec: rows: %s", err.Error())
        }
 
        return nil
+
 }
 
-func Query(query string, args []any) (*sql.Rows, error) {
+func OpenDB(addr string) error {
 
-       var empty_row *sql.Rows
+       db, err := sql.Open("sqlite3", addr)
 
-       results, err := _db.Query(query, args[0:]...)
+       if err != nil {
+
+               return err
+       }
+
+       _db = db
+
+       return nil
+}
+
+func Init(initfile string) error {
+
+       tables := make([]SqliteMaster, 0)
+
+       q := `
+       
+       SELECT 
+               tbl_name 
+       FROM 
+               sqlite_master 
+       WHERE 
+               type='table'
+       `
+
+       a := []any{}
+
+       res, err := query(q, a)
 
        if err != nil {
 
-               return empty_row, fmt.Errorf("db query: %s", err.Error())
+               return fmt.Errorf("failed to get tables: %v", err)
+       }
+
+       defer res.Close()
+
+       for res.Next() {
+
+               t := SqliteMaster{}
+
+               err = res.Scan(&t.TblName)
+
+               if err != nil {
+
+                       return fmt.Errorf("failed to read table record: %v", err)
+
+               }
+
+               tables = append(tables, t)
 
        }
 
-       return results, err
+       tlen := len(tables)
+
+       if tlen == 0 {
+               log.Printf("no tables found\n")
+
+               err = createFromInitSql(initfile)
+
+               if err != nil {
+
+                       return fmt.Errorf("create from init sql: %v", err)
+               }
+
+               log.Printf("tables successfully created\n")
+
+       }
+
+       initiated = true
+
+       return nil
 
 }
 
-func Exec(query string, args []any) error {
+func createFromInitSql(initfile string) error {
 
-       result, err := _db.Exec(query, args[0:]...)
+       file_b, err := os.ReadFile(initfile)
 
        if err != nil {
-               return fmt.Errorf("db exec: %s", err.Error())
+
+               return fmt.Errorf("failed to read initfile: %v", err)
        }
 
-       _, err = result.RowsAffected()
+       q := string(file_b)
+
+       a := []any{}
+
+       err = exec(q, a)
 
        if err != nil {
+               return fmt.Errorf("failed to init: %v", err)
+       }
 
-               return fmt.Errorf("db exec: rows: %s", err.Error())
+       tables := make([]SqliteMaster, 0)
+
+       q = `
+       
+       SELECT 
+               tbl_name 
+       FROM 
+               sqlite_master 
+       WHERE 
+               type='table'
+       `
+
+       a = []any{}
+
+       res, err := query(q, a)
+
+       if err != nil {
+
+               return fmt.Errorf("failed to get tables: %v", err)
        }
 
-       return nil
+       defer res.Close()
 
-}
+       for res.Next() {
 
-func runInitSql(initfile string) error {
+               t := SqliteMaster{}
 
-       return nil
+               err = res.Scan(&t.TblName)
 
+               if err != nil {
+
+                       return fmt.Errorf("failed to read table record: %v", err)
+
+               }
+
+               tables = append(tables, t)
+
+       }
+
+       tlen := len(tables)
+
+       if tlen == 0 {
+
+               return fmt.Errorf("failed to assert tables")
+       }
+
+       return nil
 }
diff --git a/pkg/db/db_admin.go b/pkg/db/db_admin.go
new file mode 100644 (file)
index 0000000..c83b736
--- /dev/null
@@ -0,0 +1,249 @@
+package db
+
+import (
+       "database/sql"
+       "fmt"
+)
+
+type Admin struct {
+       AdminId   int
+       Id        string
+       SessionId sql.NullString
+       Pw        sql.NullString
+}
+
+func GetAdminById(id string) (*Admin, error) {
+
+       admins := []Admin{}
+
+       q := `
+       
+       SELECT 
+               admin_id,
+               id,
+               pw
+       FROM
+               admin
+       WHERE
+               id = ?
+       
+       `
+
+       a := []any{
+               id,
+       }
+
+       res, err := query(q, a)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to get admin: %v", err)
+       }
+
+       defer res.Close()
+
+       for res.Next() {
+
+               admin := Admin{}
+
+               err = res.Scan(
+                       &admin.AdminId,
+                       &admin.Id,
+                       &admin.Pw,
+               )
+
+               if err != nil {
+
+                       return nil, fmt.Errorf("failed to get admin record: %v", err)
+               }
+
+               admins = append(admins, admin)
+
+       }
+
+       rlen := len(admins)
+
+       if rlen > 1 {
+               return nil, fmt.Errorf("admin record: invalid record len: %d", rlen)
+       }
+
+       if rlen == 0 {
+               return nil, nil
+       }
+
+       return &admins[0], nil
+
+}
+
+func UpsertAdmin(id string, pw string) error {
+
+       admin, err := GetAdminById(id)
+
+       if err != nil {
+               return fmt.Errorf("failed to get admin: %v", err)
+       }
+
+       q := ""
+
+       a := []any{}
+
+       if admin == nil {
+
+               q = `
+               
+               INSERT INTO admin (
+                       id,
+                       pw
+               )
+               VALUES (
+                       ?,
+                       ?
+               )               
+               
+               `
+
+               a = append(a, id)
+               a = append(a, pw)
+
+       } else {
+
+               q = `
+               
+               UPDATE
+                       admin
+               SET
+                       id = ?,
+                       pw = ?
+               WHERE
+                       admin_id = ?
+
+               `
+               a = append(a, id)
+               a = append(a, pw)
+               a = append(a, admin.AdminId)
+
+       }
+
+       err = exec(q, a)
+
+       if err != nil {
+               return fmt.Errorf("failed to upsert admin: %v", err)
+       }
+
+       return nil
+}
+
+func GetAdminBySessionId(session_id string) (*Admin, error) {
+
+       admins := []Admin{}
+
+       q := `
+       
+       SELECT 
+               admin_id,
+               id,
+               session_id,
+               pw
+       FROM
+               admin
+       WHERE
+               session_id = ? 
+               AND session_id IS NOT NULL
+       
+       `
+
+       a := []any{
+               session_id,
+       }
+
+       res, err := query(q, a)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to get admin: %v", err)
+       }
+
+       defer res.Close()
+
+       for res.Next() {
+
+               admin := Admin{}
+
+               err = res.Scan(
+                       &admin.AdminId,
+                       &admin.Id,
+                       &admin.SessionId,
+                       &admin.Pw,
+               )
+
+               if err != nil {
+
+                       return nil, fmt.Errorf("failed to get admin record: %v", err)
+               }
+
+               admins = append(admins, admin)
+
+       }
+
+       rlen := len(admins)
+
+       if rlen != 1 {
+               return nil, nil
+       }
+
+       return &admins[0], nil
+
+}
+
+func SetAdminSessionId(id string, session_id string, signin bool) error {
+
+       admin, err := GetAdminById(id)
+
+       if err != nil {
+               return fmt.Errorf("no such admin: %s", id)
+       }
+
+       q := ""
+       a := []any{}
+
+       if signin {
+               q = `
+       
+               UPDATE
+                       admin 
+               SET
+                       session_id = ?
+               WHERE
+                       admin_id = ?
+       
+               `
+
+               a = append(a, session_id)
+               a = append(a, admin.AdminId)
+
+       } else {
+
+               q = `
+       
+               UPDATE
+                       admin 
+               SET
+                       session_id = NULL
+               WHERE
+                       admin_id = ?
+       
+               `
+
+               a = append(a, admin.AdminId)
+
+       }
+
+       err = exec(q, a)
+
+       if err != nil {
+
+               return fmt.Errorf("failed to set session: %v", err)
+       }
+
+       return nil
+}
diff --git a/pkg/db/db_media.go b/pkg/db/db_media.go
new file mode 100644 (file)
index 0000000..f27d9cf
--- /dev/null
@@ -0,0 +1,133 @@
+package db
+
+import (
+       "encoding/json"
+       "fmt"
+       "mime/multipart"
+       "os"
+       "strings"
+
+       "github.com/gin-gonic/gin"
+)
+
+var mediaPath = "./data/media/"
+
+func UploadMedia(c *gin.Context, file *multipart.FileHeader, new_filename string) error {
+
+       this_file_path := mediaPath + new_filename
+
+       err := c.SaveUploadedFile(file, this_file_path)
+
+       if err != nil {
+
+               return fmt.Errorf("failed to upload: %s", err.Error())
+       }
+
+       return nil
+}
+
+func DownloadMedia(c *gin.Context, watchId string) error {
+
+       this_media_path := mediaPath + watchId
+
+       if _, err := os.Stat(this_media_path); err != nil {
+
+               return err
+
+       }
+
+       c.File(this_media_path)
+
+       return nil
+}
+
+func DeleteMedia(media_key string) error {
+
+       this_media_path := mediaPath + media_key
+
+       err := os.Remove(this_media_path)
+
+       if err != nil {
+
+               return fmt.Errorf("failed to delete media: rm: %s", err.Error())
+       }
+
+       return nil
+}
+
+func GetAssociateMediaKeysForEditorjsSrc(rawArticle []byte) ([]string, error) {
+
+       var retlist []string
+
+       var editorjsSrc map[string]interface{}
+
+       err := json.Unmarshal(rawArticle, &editorjsSrc)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to unmarshal: %s", err.Error())
+
+       }
+
+       blocks, okay := editorjsSrc["blocks"]
+
+       if !okay {
+
+               return nil, fmt.Errorf("invalid format: %s", "no blocks")
+       }
+
+       blocksList := blocks.([]interface{})
+
+       blocksLen := len(blocksList)
+
+       for i := 0; i < blocksLen; i++ {
+
+               blockObj := blocksList[i].(map[string]interface{})
+
+               objType, okay := blockObj["type"]
+
+               if !okay {
+                       continue
+               }
+
+               if objType != "image" {
+                       continue
+               }
+
+               objData, okay := blockObj["data"]
+
+               if !okay {
+                       continue
+               }
+
+               objFields := objData.(map[string]interface{})
+
+               fileField, okay := objFields["file"]
+
+               if !okay {
+                       continue
+               }
+
+               targetProps := fileField.(map[string]interface{})
+
+               urlTarget, okay := targetProps["url"]
+
+               if !okay {
+                       continue
+               }
+
+               target := urlTarget.(string)
+
+               pathList := strings.Split(target, "/")
+
+               keyExt := pathList[len(pathList)-1]
+
+               keyExtList := strings.Split(keyExt, ".")
+
+               key := keyExtList[0]
+
+               retlist = append(retlist, key)
+       }
+
+       return retlist, nil
+}
diff --git a/pkg/db/db_story.go b/pkg/db/db_story.go
new file mode 100644 (file)
index 0000000..6b794ca
--- /dev/null
@@ -0,0 +1,201 @@
+package db
+
+import "fmt"
+
+type Story struct {
+       StoryId          int
+       Id               string
+       Title            string
+       DateMarked       string
+       PrimaryMediaName string
+       Content          string
+}
+
+func SaveStory(id string, title string, dateMarked string, primaryMediaName string, content string) error {
+
+       q := `
+       
+       INSERT INTO story (
+       
+               id,
+               title,
+               date_marked,
+               primary_media_name,
+               content
+       ) 
+       VALUES (
+               ?,
+               ?,
+               ?,
+               ?,
+               ?
+       )
+       
+       `
+
+       a := []any{
+               id,
+               title,
+               dateMarked,
+               primaryMediaName,
+               content,
+       }
+
+       err := exec(q, a)
+
+       if err != nil {
+               return fmt.Errorf("failed to save story: %v", err)
+       }
+
+       return nil
+}
+
+func GetStoryById(id string) (*Story, error) {
+
+       stories := []Story{}
+
+       q := `
+       
+       SELECT 
+               *
+       FROM
+               story
+       WHERE
+               id = ?
+       `
+
+       a := []any{
+               id,
+       }
+
+       res, err := query(q, a)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to get story by id: %v", err)
+       }
+
+       defer res.Close()
+
+       for res.Next() {
+
+               story := Story{}
+
+               err = res.Scan(
+                       &story.StoryId,
+                       &story.Id,
+                       &story.Title,
+                       &story.DateMarked,
+                       &story.PrimaryMediaName,
+                       &story.Content,
+               )
+
+               if err != nil {
+                       return nil, fmt.Errorf("failed to get story record: %v", err)
+               }
+
+               stories = append(stories, story)
+
+       }
+
+       rlen := len(stories)
+
+       if rlen != 1 {
+
+               return nil, fmt.Errorf("invalid story record len: %d", rlen)
+       }
+
+       return &stories[0], nil
+
+}
+
+func GetStoryByTitle(title string) (*Story, error) {
+
+       stories := []Story{}
+
+       q := `
+       
+       SELECT 
+               *
+       FROM
+               story
+       WHERE
+               title = ?
+       `
+
+       a := []any{
+               title,
+       }
+
+       res, err := query(q, a)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to get story by title: %v", err)
+       }
+
+       defer res.Close()
+
+       for res.Next() {
+
+               story := Story{}
+
+               err = res.Scan(
+                       &story.StoryId,
+                       &story.Id,
+                       &story.Title,
+                       &story.DateMarked,
+                       &story.PrimaryMediaName,
+                       &story.Content,
+               )
+
+               if err != nil {
+                       return nil, fmt.Errorf("failed to get story record: %v", err)
+               }
+
+               stories = append(stories, story)
+
+       }
+
+       rlen := len(stories)
+
+       if rlen != 1 {
+
+               return nil, fmt.Errorf("invalid story record len: %d", rlen)
+       }
+
+       return &stories[0], nil
+
+}
+
+func DeleteStoryByTitle(title string) (*Story, error) {
+
+       story, err := GetStoryByTitle(title)
+
+       if err != nil {
+
+               return nil, fmt.Errorf("failed to get story by title")
+       }
+
+       q := `
+       
+       DELETE
+       FROM
+               story
+       WHERE
+               story_id = ?
+       
+       `
+       a := []any{
+               story.StoryId,
+       }
+
+       err = exec(q, a)
+
+       if err != nil {
+               return nil, fmt.Errorf("failed to delete story record")
+       }
+
+       return story, nil
+
+}
index 778f64ec17cd4fd767e18d43231361d3aff70366..9fb19e71c1bf9972f40938b9dc742f3461ed8f6c 100644 (file)
@@ -1 +1,140 @@
 package api
+
+import (
+       "encoding/json"
+       "fmt"
+       "log"
+       "net/http"
+
+       pkgauth "our-wedding-rsvp/pkg/auth"
+       pkgdb "our-wedding-rsvp/pkg/db"
+
+       "github.com/gin-gonic/contrib/sessions"
+       "github.com/gin-gonic/gin"
+)
+
+type CLIENT_REQ struct {
+       Data string `json:"data"`
+}
+
+type SERVER_RESP struct {
+       Status string `json:"status"`
+       Reply  string `json:"reply"`
+}
+
+func OauthGoogleLogin(c *gin.Context) {
+
+       if pkgauth.Is0(c) {
+
+               log.Printf("oauth login: already logged in\n")
+
+               c.JSON(http.StatusBadRequest, SERVER_RESP{Status: "error", Reply: "already logged in"})
+
+               return
+
+       }
+
+       oauth_state := pkgauth.GenerateStateAuthCookie(c)
+
+       u := pkgauth.GoogleOauthConfig.AuthCodeURL(oauth_state)
+
+       c.Redirect(302, u)
+
+}
+
+func OauthGoogleCallback(c *gin.Context) {
+
+       if pkgauth.Is0(c) {
+
+               fmt.Printf("oauth callback: already logged in\n")
+
+               c.JSON(http.StatusBadRequest, SERVER_RESP{Status: "error", Reply: "already logged in"})
+
+               return
+
+       }
+
+       session := sessions.Default(c)
+
+       var session_id string
+
+       v := session.Get("RSVP")
+
+       if v == nil {
+               log.Printf("access auth failed: %s\n", "session id not found")
+               return
+       } else {
+               session_id = v.(string)
+       }
+
+       state := c.Request.FormValue("state")
+
+       if state == "" {
+               log.Printf("access auth failed: %s\n", "form state not found")
+               return
+       }
+
+       if state != session_id {
+               log.Printf("access auth failed: %s\n", "value not matching")
+               c.Redirect(302, "/")
+               return
+       }
+
+       data, err := pkgauth.GetUserDataFromGoogle(c.Request.FormValue("code"))
+       if err != nil {
+               log.Printf("access auth failed: %s\n", err.Error())
+               c.Redirect(302, "/")
+               return
+       }
+
+       var oauth_struct pkgauth.OAuthStruct
+
+       err = json.Unmarshal(data, &oauth_struct)
+
+       if err != nil {
+               log.Printf("access auth failed: %s\n", err.Error())
+               c.Redirect(302, "/")
+               return
+       }
+
+       if !oauth_struct.VERIFIED_EMAIL {
+               log.Printf("access auth failed: %s\n", err.Error())
+               c.Redirect(302, "/")
+               return
+       }
+
+       if err != nil {
+               log.Printf("access auth failed: %s\n", err.Error())
+               c.Redirect(302, "/")
+               return
+       }
+
+       as, err := pkgdb.GetAdminById(oauth_struct.EMAIL)
+
+       if as == nil {
+
+               log.Printf("access auth failed: %s", err.Error())
+
+               c.Redirect(302, "/")
+
+               return
+
+       }
+
+       err = pkgdb.SetAdminSessionId(oauth_struct.EMAIL, session_id, true)
+
+       if err != nil {
+
+               log.Printf("make session failed for admin: %s", err.Error())
+
+               c.Redirect(302, "/")
+
+               return
+
+       }
+
+       log.Println("oauth sign in success")
+
+       c.Redirect(302, "/")
+
+}
index 669e9911597765f51a4f8cb91cd1c3895106911a..5a3de858ff938f94adfacc9a82912b5e7d7c43a8 100644 (file)
@@ -3,6 +3,8 @@ package server
 import (
        "github.com/gin-gonic/contrib/sessions"
        "github.com/gin-gonic/gin"
+
+       pkgserverapi "our-wedding-rsvp/pkg/server/api"
 )
 
 func CreateServerFromConfig() (*gin.Engine, error) {
@@ -34,6 +36,10 @@ func configureServer(e *gin.Engine) error {
 
        e.GET("/signin", getSignin)
 
+       e.GET("/api/oauth2/google/signin", pkgserverapi.OauthGoogleLogin)
+
+       e.GET("/oauth2/google/callback", pkgserverapi.OauthGoogleCallback)
+
        e.GET("/story/r/:storyId", getRead)
 
        e.GET("/story/w", getWrite)
index 4d4855e6e5a53af2d77da352ef32852e7bbe16f9..bb9a8392317522710066a9fc6aafe35bc146e6b9 100644 (file)
@@ -2,6 +2,8 @@ package server
 
 import (
        "github.com/gin-gonic/gin"
+
+       pkgauth "our-wedding-rsvp/pkg/auth"
 )
 
 func getIndex(c *gin.Context) {
@@ -12,6 +14,14 @@ func getIndex(c *gin.Context) {
 
 func getSignin(c *gin.Context) {
 
+       if pkgauth.USE_OAUTH2 {
+
+               c.Redirect(302, "/api/oauth2/google/signin")
+
+               return
+
+       }
+
        c.HTML(200, "signin.html", gin.H{})
 }
 
@@ -22,5 +32,10 @@ func getRead(c *gin.Context) {
 
 func getWrite(c *gin.Context) {
 
+       if !pkgauth.Is0(c) {
+               c.HTML(200, "index.html", gin.H{})
+               return
+       }
+
        c.HTML(200, "write.html", gin.H{})
 }
index f9fa3ef051dca83c2edaccf3170efc2ee9848440..6f1a8b9261ad20674254186f9701539f4d0ada02 100755 (executable)
--- a/start.sh
+++ b/start.sh
@@ -1,14 +1,7 @@
 #!/bin/bash
 
 
-mkdir -p data
-
-if [  ! -f "data/rsvp.db" ]
-then
-
-    sqlite3 data/rsvp.db "VACUUM;"
-
-fi
+./start_mkdb.sh
 
 ./rsvp.out
 
diff --git a/start_mkdb.sh b/start_mkdb.sh
new file mode 100755 (executable)
index 0000000..7937645
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+
+mkdir -p data
+
+mkdir -p data/media
+
+
+if [  ! -f "data/rsvp.db" ]
+then
+
+    sqlite3 data/rsvp.db "VACUUM;"
+
+fi
+
+
+