From 3337ebb47554800e2b00558ff9ab01a8bfe51dd4 Mon Sep 17 00:00:00 2001 From: Spotlight Date: Sat, 1 Jan 2022 23:00:50 -0600 Subject: [PATCH] Properly check registration status --- .gitignore | 1 + README.md | 14 ++++++++---- constants.go | 44 ++++++++++++++++++++++++++++++++++++ database.sql | 22 +++++------------- ias.go | 64 +++++++++++++++++++++++++++++++++++++--------------- structure.go | 29 ------------------------ 6 files changed, 106 insertions(+), 68 deletions(-) create mode 100644 constants.go diff --git a/.gitignore b/.gitignore index 4e68a7f..954aced 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea config.xml +WiiSOAP diff --git a/README.md b/README.md index 4d20c6d..3bb7f01 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,20 @@ # WiiSOAP -A SOAP Server, designed specifically to handle Wii Shop Channel SOAP. Slowly in development. -> WiiSOAP is being rewritten in the future, therefore Pull Requests may be ignored. +WiiSOAP is a server designed specifically to handle Wii Shop Channel SOAP - more specifically, that of the ECommerce library. +Ideally, one day this will become feature complete enough to handle other titles utilizing EC, such as DLCs or other purchases. -## What's the difference between this repo and that other SOAP repo? -This is the SOAP Server Software. The other repository only has the communication templates between a Wii and WSC's server. +It aims to implement everything necessary to provide title tickets, manage authentication, and everything between. + +> Note that this software is still in development. The schema ma + +## What's the difference between this repo and [that other SOAP repo](https://github.com/OpenShopChannel/Open-Shop-SOAP)? +This is the SOAP Server Software. The other repository, [Open-Shop-SOAP](https://github.com/OpenShopChannel/Open-Shop-SOAP), holds templates of communication between a Wii and WSC's server. # Changelog Versions on this software are based on goals. (e.g 0.2 works towards SQL support. 0.3 works towards NUS support, etc.) ## 0.3.x Tanuki ### 0.3.0 -- Migrates to using PostgreSQL +- Migrate to using PostgreSQL - Add routes and XML niceties - Implement most routes diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..523bc0e --- /dev/null +++ b/constants.go @@ -0,0 +1,44 @@ +package main + +// LimitKinds represents various limits applied to the current ticket. +type LimitKinds int + +const ( + // PR is presumably "purchased". + PR LimitKinds = 0 + TR = 1 + DR = 2 + SR = 3 + LR = 4 + AT = 10000 +) + +// LimitStruct returns a Limits struct filled for the given kind. +func LimitStruct(kind LimitKinds) Limits { + names := map[LimitKinds]string{ + PR: "PR", + TR: "TR", + DR: "DR", + SR: "SR", + LR: "LR", + AT: "AT", + } + + return Limits{ + Limits: kind, + LimitKind: names[kind], + } +} + +// DeviceStatus represents the various statuses a device may have. +// +// These values do not appear to be directly checked by the client within the +// Wii Shop Channel, and are a generic string. We could utilize any value we wish. +// However, titles utilizing DLCs appear to check the raw values. +// For this reason, we mirror values from Nintendo. +type DeviceStatus string + +const ( + DeviceStatusRegistered = "R" + DeviceStatusUnregistered = "U" +) diff --git a/database.sql b/database.sql index 70687cf..4c5aa02 100644 --- a/database.sql +++ b/database.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 13.2 --- Dumped by pg_dump version 13.2 +-- Dumped from database version 14.1 +-- Dumped by pg_dump version 14.1 SET statement_timeout = 0; SET lock_timeout = 0; @@ -64,22 +64,12 @@ CREATE TABLE public.userbase ( device_token_hashed character varying(32) NOT NULL, account_id integer NOT NULL, region character varying(3), - country character varying(2), - language character varying(2), - serial_number character varying(11), - device_code bigint + serial_number character varying(11) ); ALTER TABLE public.userbase OWNER TO wiisoap; --- --- Name: COLUMN userbase.device_code; Type: COMMENT; Schema: public; Owner: wiisoap --- - -COMMENT ON COLUMN public.userbase.device_code IS 'Also known as the console''s friend code.'; - - -- -- Name: owned_titles owned_titles_pk; Type: CONSTRAINT; Schema: public; Owner: wiisoap -- @@ -126,10 +116,10 @@ CREATE UNIQUE INDEX userbase_account_id_uindex ON public.userbase USING btree (a -- --- Name: userbase_device_code_uindex; Type: INDEX; Schema: public; Owner: wiisoap +-- Name: userbase_device_id_uindex; Type: INDEX; Schema: public; Owner: wiisoap -- -CREATE UNIQUE INDEX userbase_device_code_uindex ON public.userbase USING btree (device_code); +CREATE UNIQUE INDEX userbase_device_id_uindex ON public.userbase USING btree (device_id); -- @@ -157,4 +147,4 @@ ALTER TABLE ONLY public.owned_titles -- -- PostgreSQL database dump complete --- \ No newline at end of file +-- diff --git a/ias.go b/ias.go index a82fcdb..c25215d 100644 --- a/ias.go +++ b/ias.go @@ -23,25 +23,55 @@ import ( "fmt" wiino "github.com/RiiConnect24/wiino/golang" "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" "log" "math/rand" "strconv" ) const ( - PrepareUserStatement = `INSERT INTO userbase (device_id, device_token, device_token_hashed, account_id, region, country, language, serial_number, device_code) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)` - SyncUserStatement = `SELECT account_id, device_code, device_token FROM userbase WHERE language = $1 AND country = $2 AND region = $3 AND device_id = $4` + PrepareUserStatement = `INSERT INTO userbase + (device_id, device_token, device_token_hashed, account_id, region, serial_number) + VALUES ($1, $2, $3, $4, $5, $6)` + SyncUserStatement = `SELECT + account_id, device_token + FROM userbase WHERE + region = $1 AND + device_id = $2` + CheckUserStatement = `SELECT + 1 + FROM userbase WHERE + device_id = $1 AND + serial_number = $2 AND + region = $3` ) func checkRegistration(e *Envelope) { serialNo, err := getKey(e.doc, "SerialNumber") if err != nil { - e.Error(5, "not good enough for me. ;3", err) + e.Error(5, "missing serial number", err) return } + // We'll utilize our sync user statement. + query := pool.QueryRow(ctx, CheckUserStatement, e.DeviceId(), serialNo, e.Region()) + err = query.Scan(nil) + + // Formulate our response e.AddKVNode("OriginalSerialNumber", serialNo) - e.AddKVNode("DeviceStatus", "R") + + if err != nil { + // We're either unregistered, or a database error occurred. + if err == pgx.ErrNoRows { + e.AddKVNode("DeviceStatus", DeviceStatusUnregistered) + } else { + log.Printf("error executing statement: %v\n", err) + e.Error(5, "server-side error", err) + } + } else { + // No errors! We're safe. + e.AddKVNode("DeviceStatus", DeviceStatusRegistered) + } } func getChallenge(e *Envelope) { @@ -64,11 +94,10 @@ func getRegistrationInfo(e *Envelope) { func syncRegistration(e *Envelope) { var accountId int64 - var deviceCode int var deviceToken string - user := pool.QueryRow(ctx, SyncUserStatement, e.Language(), e.Country(), e.Region(), e.DeviceId()) - err := user.Scan(&accountId, &deviceCode, &deviceToken) + user := pool.QueryRow(ctx, SyncUserStatement, e.Region(), e.DeviceId()) + err := user.Scan(&accountId, &deviceToken) if err != nil { e.Error(7, "An error occurred querying the database.", err) } @@ -82,37 +111,36 @@ func syncRegistration(e *Envelope) { } func register(e *Envelope) { - reason := "disgustingly invalid. ;3" deviceCode, err := getKey(e.doc, "DeviceCode") if err != nil { - e.Error(7, reason, err) + e.Error(7, "missing device code", err) return } registerRegion, err := getKey(e.doc, "RegisterRegion") if err != nil { - e.Error(7, reason, err) + e.Error(7, "missing registration region", err) return } if registerRegion != e.Region() { - e.Error(7, reason, errors.New("region does not match registration region")) + e.Error(7, "mismatched region", errors.New("region does not match registration region")) return } serialNo, err := getKey(e.doc, "SerialNumber") if err != nil { - e.Error(7, reason, err) + e.Error(7, "missing serial number", err) return } // Validate given friend code. userId, err := strconv.ParseUint(deviceCode, 10, 64) if err != nil { - e.Error(7, reason, err) + e.Error(7, "invalid friend code", err) return } if wiino.NWC24CheckUserID(userId) != 0 { - e.Error(7, reason, err) + e.Error(7, "invalid friend code", err) return } @@ -124,18 +152,18 @@ func register(e *Envelope) { // ...and then its md5, because the Wii sends this for most requests. md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) - // Insert all of our obtained values to the database.. - _, err = pool.Exec(ctx, PrepareUserStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.Region(), e.Country(), e.Language(), serialNo, deviceCode) + // Insert all of our obtained values to the database... + _, err = pool.Exec(ctx, PrepareUserStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.Region(), serialNo) if err != nil { // It's okay if this isn't a PostgreSQL error, as perhaps other issues have come in. if driverErr, ok := err.(*pgconn.PgError); ok { if driverErr.Code == "23505" { - e.Error(7, reason, errors.New("user already exists")) + e.Error(7, "database error", errors.New("user already exists")) return } } log.Printf("error executing statement: %v\n", err) - e.Error(7, reason, errors.New("failed to execute db operation")) + e.Error(7, "database error", errors.New("failed to execute db operation")) return } diff --git a/structure.go b/structure.go index 217714a..1b7c270 100644 --- a/structure.go +++ b/structure.go @@ -101,35 +101,6 @@ type Balance struct { Currency string `xml:"Currency"` } -type LimitKinds int - -const ( - // PR is presumably "purchased". - PR LimitKinds = 0 - TR = 1 - DR = 2 - SR = 3 - LR = 4 - AT = 10000 -) - -// LimitStruct returns a Limits struct filled for the given kind. -func LimitStruct(kind LimitKinds) Limits { - names := map[LimitKinds]string{ - PR: "PR", - TR: "TR", - DR: "DR", - SR: "SR", - LR: "LR", - AT: "AT", - } - - return Limits{ - Limits: kind, - LimitKind: names[kind], - } -} - // Limits represents a common XML structure for transaction information. type Limits struct { XMLName xml.Name `xml:"Limits"`