Properly check registration status

This commit is contained in:
Spotlight 2022-01-01 23:00:50 -06:00
parent 943f475bba
commit 3337ebb475
No known key found for this signature in database
GPG Key ID: 874AA355B3209BDC
6 changed files with 106 additions and 68 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.idea .idea
config.xml config.xml
WiiSOAP

View File

@ -1,16 +1,20 @@
# WiiSOAP # WiiSOAP
A SOAP Server, designed specifically to handle Wii Shop Channel SOAP. Slowly in development. WiiSOAP is a server designed specifically to handle Wii Shop Channel SOAP - more specifically, that of the ECommerce library.
> WiiSOAP is being rewritten in the future, therefore Pull Requests may be ignored. 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? It aims to implement everything necessary to provide title tickets, manage authentication, and everything between.
This is the SOAP Server Software. The other repository only has the communication templates between a Wii and WSC's server.
> 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 # Changelog
Versions on this software are based on goals. (e.g 0.2 works towards SQL support. 0.3 works towards NUS support, etc.) 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.x Tanuki
### 0.3.0 ### 0.3.0
- Migrates to using PostgreSQL - Migrate to using PostgreSQL
- Add routes and XML niceties - Add routes and XML niceties
- Implement most routes - Implement most routes

44
constants.go Normal file
View File

@ -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"
)

View File

@ -2,8 +2,8 @@
-- PostgreSQL database dump -- PostgreSQL database dump
-- --
-- Dumped from database version 13.2 -- Dumped from database version 14.1
-- Dumped by pg_dump version 13.2 -- Dumped by pg_dump version 14.1
SET statement_timeout = 0; SET statement_timeout = 0;
SET lock_timeout = 0; SET lock_timeout = 0;
@ -64,22 +64,12 @@ CREATE TABLE public.userbase (
device_token_hashed character varying(32) NOT NULL, device_token_hashed character varying(32) NOT NULL,
account_id integer NOT NULL, account_id integer NOT NULL,
region character varying(3), region character varying(3),
country character varying(2), serial_number character varying(11)
language character varying(2),
serial_number character varying(11),
device_code bigint
); );
ALTER TABLE public.userbase OWNER TO wiisoap; 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 -- 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 -- PostgreSQL database dump complete
-- --

64
ias.go
View File

@ -23,25 +23,55 @@ import (
"fmt" "fmt"
wiino "github.com/RiiConnect24/wiino/golang" wiino "github.com/RiiConnect24/wiino/golang"
"github.com/jackc/pgconn" "github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"log" "log"
"math/rand" "math/rand"
"strconv" "strconv"
) )
const ( 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)` PrepareUserStatement = `INSERT INTO userbase
SyncUserStatement = `SELECT account_id, device_code, device_token FROM userbase WHERE language = $1 AND country = $2 AND region = $3 AND device_id = $4` (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) { func checkRegistration(e *Envelope) {
serialNo, err := getKey(e.doc, "SerialNumber") serialNo, err := getKey(e.doc, "SerialNumber")
if err != nil { if err != nil {
e.Error(5, "not good enough for me. ;3", err) e.Error(5, "missing serial number", err)
return 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("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) { func getChallenge(e *Envelope) {
@ -64,11 +94,10 @@ func getRegistrationInfo(e *Envelope) {
func syncRegistration(e *Envelope) { func syncRegistration(e *Envelope) {
var accountId int64 var accountId int64
var deviceCode int
var deviceToken string var deviceToken string
user := pool.QueryRow(ctx, SyncUserStatement, e.Language(), e.Country(), e.Region(), e.DeviceId()) user := pool.QueryRow(ctx, SyncUserStatement, e.Region(), e.DeviceId())
err := user.Scan(&accountId, &deviceCode, &deviceToken) err := user.Scan(&accountId, &deviceToken)
if err != nil { if err != nil {
e.Error(7, "An error occurred querying the database.", err) e.Error(7, "An error occurred querying the database.", err)
} }
@ -82,37 +111,36 @@ func syncRegistration(e *Envelope) {
} }
func register(e *Envelope) { func register(e *Envelope) {
reason := "disgustingly invalid. ;3"
deviceCode, err := getKey(e.doc, "DeviceCode") deviceCode, err := getKey(e.doc, "DeviceCode")
if err != nil { if err != nil {
e.Error(7, reason, err) e.Error(7, "missing device code", err)
return return
} }
registerRegion, err := getKey(e.doc, "RegisterRegion") registerRegion, err := getKey(e.doc, "RegisterRegion")
if err != nil { if err != nil {
e.Error(7, reason, err) e.Error(7, "missing registration region", err)
return return
} }
if registerRegion != e.Region() { 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 return
} }
serialNo, err := getKey(e.doc, "SerialNumber") serialNo, err := getKey(e.doc, "SerialNumber")
if err != nil { if err != nil {
e.Error(7, reason, err) e.Error(7, "missing serial number", err)
return return
} }
// Validate given friend code. // Validate given friend code.
userId, err := strconv.ParseUint(deviceCode, 10, 64) userId, err := strconv.ParseUint(deviceCode, 10, 64)
if err != nil { if err != nil {
e.Error(7, reason, err) e.Error(7, "invalid friend code", err)
return return
} }
if wiino.NWC24CheckUserID(userId) != 0 { if wiino.NWC24CheckUserID(userId) != 0 {
e.Error(7, reason, err) e.Error(7, "invalid friend code", err)
return return
} }
@ -124,18 +152,18 @@ func register(e *Envelope) {
// ...and then its md5, because the Wii sends this for most requests. // ...and then its md5, because the Wii sends this for most requests.
md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken)))
// Insert all of our obtained values to the database.. // 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) _, err = pool.Exec(ctx, PrepareUserStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.Region(), serialNo)
if err != nil { if err != nil {
// It's okay if this isn't a PostgreSQL error, as perhaps other issues have come in. // 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, ok := err.(*pgconn.PgError); ok {
if driverErr.Code == "23505" { if driverErr.Code == "23505" {
e.Error(7, reason, errors.New("user already exists")) e.Error(7, "database error", errors.New("user already exists"))
return return
} }
} }
log.Printf("error executing statement: %v\n", err) 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 return
} }

View File

@ -101,35 +101,6 @@ type Balance struct {
Currency string `xml:"Currency"` 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. // Limits represents a common XML structure for transaction information.
type Limits struct { type Limits struct {
XMLName xml.Name `xml:"Limits"` XMLName xml.Name `xml:"Limits"`