package cracha import ( "database/sql" "fmt" "os" "time" // "q" "golite" "guuid" "scrypt" g "gobang" ) type userDataT struct{ userID guuid.UUID email string salt []byte pwhash []byte token string } func mksalt() []byte { salt, err := scrypt.Salt() g.TErrorIf(err) return salt } func newUser() userDataT { return userDataT{ userID: guuid.New(), email: string(mksalt()), salt: mksalt(), pwhash: mksalt(), token: string(mksalt()), } } func test_defaultPrefix() { g.TestStart("defaultPrefix") g.Testing("the defaultPrefix is valid", func() { g.TErrorIf(g.ValidateSQLTablePrefix(defaultPrefix)) }) } func test_tryRollback() { // FIXME } func test_inTx() { // FIXME } func test_createTables() { g.TestStart("createTables()") db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) defer db.Close() g.Testing("tables exist afterwards", func() { const tmpl_read = ` SELECT id FROM "%s_users" LIMIT 1; ` qRead := fmt.Sprintf(tmpl_read, defaultPrefix) _, err := db.Exec(qRead) g.TErrorNil(err) err = createTables(db, defaultPrefix) g.TErrorIf(err) _, err = db.Exec(qRead) g.TErrorIf(err) }) g.Testing("we can do it multiple times", func() { g.TErrorIf(g.SomeError( createTables(db, defaultPrefix), createTables(db, defaultPrefix), createTables(db, defaultPrefix), )) }) } func test_registerStmt() { g.TestStart("registerStmt()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) g.TErrorIf(registerErr) defer g.SomeFnError( registerClose, db.Close, ) g.Testing("we can register a user", func() { u := newUser() user, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TAssertEqual(user.timestamp == time.Time{}, false) g.TAssertEqual(user.id, int64(1)) g.TAssertEqual(user.uuid, u.userID) g.TAssertEqual(user.email, u.email) g.TAssertEqual(user.salt, u.salt) g.TAssertEqual(user.pwhash, u.pwhash) }) g.Testing("users can't have the same uuid", func() { u1 := newUser() u2 := newUser() userID := guuid.New() _, err1 := register(userID, u1.email, u1.salt, u1.pwhash) _, err2 := register(userID, u2.email, u2.salt, u2.pwhash) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("users can't have the same email", func() { u1 := newUser() u2 := newUser() email := string(mksalt()) _, err1 := register(u1.userID, email, u1.salt, u1.pwhash) _, err2 := register(u2.userID, email, u2.salt, u2.pwhash) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("users can't have the same salt", func() { u1 := newUser() u2 := newUser() salt := mksalt() _, err1 := register(u1.userID, u1.email, salt, u1.pwhash) _, err2 := register(u2.userID, u2.email, salt, u2.pwhash) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("no error when close()ing more than once", func() { g.TErrorIf(g.SomeError( registerClose(), registerClose(), registerClose(), )) }) } func test_sendTokenStmt() { g.TestStart("sendToken()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) sendToken, sendTokenClose, sendTokenErr := sendTokenStmt(db, prefix) g.TErrorIf(g.SomeError( registerErr, sendTokenErr, )) defer g.SomeFnError( registerClose, sendTokenClose, db.Close, ) g.Testing("can't send a token to a non-existent user", func() { err := sendToken(guuid.New(), "some token") g.TAssertEqual( err.(golite.Error).ExtendedCode, golite.ErrConstraintNotNull, ) }) g.Testing("otherwise creates the confirmation attempt", func() { u := newUser() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) err = sendToken(u.userID, u.token) g.TErrorIf(err) }) g.Testing("token has to be unique globally", func() { u1 := newUser() u2 := newUser() token := string(mksalt()) _, err := register(u1.userID, u1.email, u1.salt, u1.pwhash) g.TErrorIf(err) _, err = register(u2.userID, u2.email, u2.salt, u2.pwhash) g.TErrorIf(err) err1 := sendToken(u1.userID, token) err2 := sendToken(u2.userID, token) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("a user can have multiple", func() { u := newUser() token1 := string(mksalt()) token2 := string(mksalt()) token3 := string(mksalt()) _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TErrorIf(g.SomeError( sendToken(u.userID, token1), sendToken(u.userID, token2), sendToken(u.userID, token3), )) tokens := []string{} const tmpl_read = ` SELECT token from "%s_confirmation_attempts" WHERE user_id = ( SELECT id FROM "%s_users" WHERE uuid = ? ) ORDER BY id ASC; ` qRead := fmt.Sprintf(tmpl_read, prefix, prefix) rows, err := db.Query(qRead, u.userID[:]) g.TErrorIf(err) for rows.Next() { var token string err := rows.Scan(&token) g.TErrorIf(err) tokens = append(tokens, token) } g.TErrorIf(g.SomeFnError(rows.Err, rows.Close)) expected := []string{ token1, token2, token3, } g.TAssertEqual(tokens, expected) }) } func test_confirmStmt() { g.TestStart("confirmStmt()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) sendToken, sendTokenClose, sendTokenErr := sendTokenStmt(db, prefix) confirm, confirmClose, confirmErr := confirmStmt(db, prefix) g.TErrorIf(g.SomeError( registerErr, sendTokenErr, confirmErr, )) defer g.SomeFnError( registerClose, sendTokenClose, confirmClose, db.Close, ) g.Testing("can't confirm a token that doesn't exist", func() { _, err := confirm(string(mksalt()), guuid.New()) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("otherwise it creates a confirmation and a session", func() { u := newUser() sessionID := guuid.New() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TErrorIf(sendToken(u.userID, u.token)) session, err := confirm(u.token, sessionID) g.TErrorIf(err) g.TAssertEqual(session.timestamp == time.Time{}, false) g.TAssertEqual(session.uuid, sessionID) g.TAssertEqual(session.userID, u.userID) }) g.Testing("can't confirm the same token twice", func() { u := newUser() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TErrorIf(sendToken(u.userID, u.token)) _, err1 := confirm(u.token, guuid.New()) _, err2 := confirm(u.token, guuid.New()) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("a user can't have 2 confirmations", func() { u := newUser() token1 := string(mksalt()) token2 := string(mksalt()) _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TErrorIf(g.SomeError( sendToken(u.userID, token1), sendToken(u.userID, token2), )) _, err1 := confirm(token1, guuid.New()) _, err2 := confirm(token2, guuid.New()) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) } func test_byEmailStmt() { g.TestStart("byEmailStmt()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) sendToken, sendTokenClose, sendTokenErr := sendTokenStmt(db, prefix) confirm, confirmClose, confirmErr := confirmStmt(db, prefix) byEmail, byEmailClose, byEmailErr := byEmailStmt(db, prefix) g.TErrorIf(g.SomeError( registerErr, sendTokenErr, confirmErr, byEmailErr, )) defer g.SomeFnError( registerClose, sendTokenClose, confirmClose, byEmailClose, db.Close, ) g.Testing("error when not found", func() { email := string(mksalt()) _, err := byEmail(email) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("full user otherwise, confirmed or not", func() { u := newUser() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) user1, err := byEmail(u.email) g.TErrorIf(err) g.TErrorIf(sendToken(u.userID, u.token)) user2, err := byEmail(u.email) g.TErrorIf(err) _, err = confirm(u.token, guuid.New()) g.TErrorIf(err) user3, err := byEmail(u.email) g.TErrorIf(err) g.TAssertEqual(user1, user2) user2.confirmed = true g.TAssertEqual(user2, user3) }) } func test_loginStmt() { g.TestStart("loginStmt()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) sendToken, sendTokenClose, sendTokenErr := sendTokenStmt(db, prefix) confirm, confirmClose, confirmErr := confirmStmt(db, prefix) byEmail, byEmailClose, byEmailErr := byEmailStmt(db, prefix) login, loginClose, loginErr := loginStmt(db, prefix) g.TErrorIf(g.SomeError( registerErr, sendTokenErr, confirmErr, byEmailErr, loginErr, )) defer g.SomeFnError( registerClose, sendTokenClose, confirmClose, byEmailClose, loginClose, db.Close, ) g.Testing("a user must exist to login", func() { _, err := login(guuid.New(), guuid.New()) g.TAssertEqual( err.(golite.Error).ExtendedCode, golite.ErrConstraintNotNull, ) }) g.Testing("sessionID must be unique globally", func() { u1 := newUser() u2 := newUser() sessionID := guuid.New() _, err := register(u1.userID, u1.email, u1.salt, u1.pwhash) g.TErrorIf(err) _, err = register(u2.userID, u2.email, u2.salt, u2.pwhash) g.TErrorIf(err) _, err1 := login(u1.userID, sessionID) _, err2 := login(u1.userID, sessionID) _, err3 := login(u2.userID, sessionID) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) g.TAssertEqual( err3.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("a user can have multiple active sessions", func() { u := newUser() sessionID1 := guuid.New() sessionID2 := guuid.New() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) session1, err := login(u.userID, sessionID1) g.TErrorIf(err) session2, err := login(u.userID, sessionID2) g.TErrorIf(err) g.TAssertEqual(session1.uuid, sessionID1) g.TAssertEqual(session2.uuid, sessionID2) g.TAssertEqual(session1.userID, u.userID) g.TAssertEqual(session2.userID, u.userID) }) g.Testing("multiple users can be logged in", func() { u1 := newUser() u2 := newUser() sessionID1 := guuid.New() sessionID2 := guuid.New() _, err := register(u1.userID, u1.email, u1.salt, u1.pwhash) g.TErrorIf(err) _, err = register(u2.userID, u2.email, u2.salt, u2.pwhash) g.TErrorIf(err) session1, err := login(u1.userID, sessionID1) g.TErrorIf(err) session2, err := login(u2.userID, sessionID2) g.TErrorIf(err) g.TAssertEqual(session1.uuid, sessionID1) g.TAssertEqual(session2.uuid, sessionID2) g.TAssertEqual(session1.userID, u1.userID) g.TAssertEqual(session2.userID, u2.userID) }) g.Testing("an unconfirmed user is allowed to login", func() { u := newUser() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) _, err = login(u.userID, guuid.New()) g.TErrorIf(err) }) g.Testing("a confirmed user is allowed to login, too", func() { u := newUser() _, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) g.TErrorIf(sendToken(u.userID, u.token)) _, err = confirm(u.token, guuid.New()) g.TErrorIf(err) user, err := byEmail(u.email) g.TErrorIf(err) g.TAssertEqual(user.confirmed, true) _, err = login(u.userID, guuid.New()) g.TErrorIf(err) }) } func test_refreshStmt() { g.TestStart("refreshStmt()") const ( prefix = defaultPrefix ) db, err := sql.Open(golite.DriverName, ":memory:") g.TErrorIf(err) g.TErrorIf(createTables(db, prefix)) register, registerClose, registerErr := registerStmt(db, prefix) sendToken, sendTokenClose, sendTokenErr := sendTokenStmt(db, prefix) confirm, confirmClose, confirmErr := confirmStmt(db, prefix) byEmail, byEmailClose, byEmailErr := byEmailStmt(db, prefix) login, loginClose, loginErr := loginStmt(db, prefix) refresh, refreshClose, refreshErr := refreshStmt(db, prefix) g.TErrorIf(g.SomeError( registerErr, sendTokenErr, confirmErr, byEmailErr, loginErr, refreshErr, )) defer g.SomeFnError( registerClose, sendTokenClose, confirmClose, byEmailClose, loginClose, refreshClose, db.Close, ) reg := func(u userDataT) userT { user, err := register(u.userID, u.email, u.salt, u.pwhash) g.TErrorIf(err) return user } conf := func(u userDataT) sessionT { err := sendToken(u.userID, u.token) g.TErrorIf(err) session, err := confirm(u.token, guuid.New()) g.TErrorIf(err) return session } g.Testing("a session needs to exist be be refreshed", func() { _, err := refresh(guuid.New(), guuid.New()) g.TAssertEqual( err.(golite.Error).ExtendedCode, golite.ErrConstraintNotNull, ) }) g.Testing("we can refresh the session of an unconfirmed user", func() { u := newUser() sessionID1 := guuid.New() sessionID2 := guuid.New() reg(u) session1, err := login(u.userID, sessionID1) g.TErrorIf(err) user, err := byEmail(u.email) g.TErrorIf(err) g.TAssertEqual(user.confirmed, false) session2, err := refresh(sessionID1, sessionID2) g.TErrorIf(err) g.TAssertEqual(session1.userID, u.userID) g.TAssertEqual(session1.userID, session2.userID) g.TAssertEqual(session1.uuid, sessionID1) g.TAssertEqual(session2.uuid, sessionID2) g.TAssertEqual(session1.timestamp == time.Time{}, false) g.TAssertEqual(session2.timestamp == time.Time{}, false) }) g.Testing("we can refresh the session of a confirmed user", func() { u := newUser() sessionID1 := guuid.New() sessionID2 := guuid.New() reg(u) session1 := conf(u) user, err := byEmail(u.email) g.TErrorIf(err) g.TAssertEqual(user.confirmed, true) // FIXME return session2, err := refresh(sessionID1, sessionID2) g.TErrorIf(err) g.TAssertEqual(session1.userID, u.userID) g.TAssertEqual(session1.userID, session2.userID) g.TAssertEqual(session1.uuid, sessionID1) g.TAssertEqual(session2.uuid, sessionID2) }) g.Testing("we can't refresh an expired session", func() { }) g.Testing("the sessionID can't be reused, even across users", func() { }) // FIXME } func test_resetStmt() { // FIXME } func test_changeStmt() { // FIXME } func test_byUUIDStmt() { // FIXME } func test_logoutStmt() { // FIXME } func test_outOthersStmt() { // FIXME } func test_outAllStmt() { // FIXME } func test_initDB() { // FIXME } func test_queriesTclose() { // FIXME } func test_newUserHandler() { // FIXME } func test_sendConfirmationRequestHandler() { // FIXME } func test_forgotPasswordRequestHandler() { // FIXME } func test_registerConsumers() { // FIXME } func test_unregisterConsumers() { // FIXME } func test_startRunner() { // FIXME } func test_makePoolRunner() { // FIXME } func test_asResult() { // FIXME } func test_unwrapResult() { // FIXME } func test_NewWithPrefix() { // FIXME } func test_New() { // FIXME } func test_newUserPayload() { // FIXME } func test_authT_Register() { // FIXME } func test_sendConfirmationMessage() { // FIXME } func test_authT_ResentConfirmation() { // FIXME } func test_authT_ConfirmEmail() { // FIXME } func test_authT_LoginEmail() { // FIXME } func test_forgotPasswordMessage() { // FIXME } func test_authT_ForgotPassword() { // FIXME } func test_checkSession() { // FIXME } func test_validateSession() { // FIXME } func test_authT_Refresh() { // FIXME } func test_authT_ResetPassword() { // FIXME } func test_authT_ChangePassword() { // FIXME } func test_runLogout() { // FIXME } func test_authT_Logout() { // FIXME } func test_authT_LogoutOthers() { // FIXME } func test_authT_LogoutAll() { // FIXME } func test_authT_Close() { // FIXME } func test_usage() { // FIXME } func test_getopt() { // FIXME } func test_runCommand() { // FIXME } func dumpQueries() { queries := []struct{name string; fn func(string) queryT}{ { "createTables", createTablesSQL }, { "byEmail", byEmailSQL }, { "register", registerSQL }, { "sendToken", sendTokenSQL }, { "confirm", confirmSQL }, { "login", loginSQL }, { "refresh", refreshSQL }, { "reset", resetSQL }, { "change", changeSQL }, { "byUUID", byUUIDSQL }, { "logout", logoutSQL }, { "outOthers", outOthersSQL }, { "outAll", outAllSQL }, } for _, query := range queries { q := query.fn(defaultPrefix) fmt.Printf("\n-- %s.sql:", query.name) fmt.Printf("\n-- write:%s\n", q.write) fmt.Printf("\n-- read:%s\n", q.read) } } func MainTest() { if os.Getenv("TESTING_DUMP_SQL_QUERIES") != "" { dumpQueries() return } g.Init() test_defaultPrefix() test_tryRollback() test_inTx() test_createTables() test_registerStmt() test_sendTokenStmt() test_confirmStmt() test_byEmailStmt() test_loginStmt() test_refreshStmt() test_resetStmt() test_changeStmt() test_byUUIDStmt() test_logoutStmt() test_outOthersStmt() test_outAllStmt() test_initDB() test_queriesTclose() test_newUserHandler() test_sendConfirmationRequestHandler() test_forgotPasswordRequestHandler() test_registerConsumers() test_unregisterConsumers() test_startRunner() test_makePoolRunner() test_asResult() test_unwrapResult() test_NewWithPrefix() test_New() test_newUserPayload() test_authT_Register() test_sendConfirmationMessage() test_authT_ResentConfirmation() test_authT_ConfirmEmail() test_authT_LoginEmail() test_forgotPasswordMessage() test_authT_ForgotPassword() test_checkSession() test_validateSession() test_authT_Refresh() test_authT_ResetPassword() test_authT_ChangePassword() test_runLogout() test_authT_Logout() test_authT_LogoutOthers() test_authT_LogoutAll() test_authT_Close() test_usage() test_getopt() test_runCommand() }