package com.intrbiz.bergamot.model; import java.io.Serializable; import java.security.SecureRandom; import java.sql.Timestamp; import java.util.UUID; import org.apache.commons.codec.binary.Base32; import com.intrbiz.bergamot.data.BergamotDB; import com.intrbiz.data.db.compiler.meta.Action; import com.intrbiz.data.db.compiler.meta.SQLColumn; import com.intrbiz.data.db.compiler.meta.SQLForeignKey; import com.intrbiz.data.db.compiler.meta.SQLPrimaryKey; import com.intrbiz.data.db.compiler.meta.SQLTable; import com.intrbiz.data.db.compiler.meta.SQLVersion; /** * A single use backup code for as a two factor auth fall back */ @SQLTable(schema = BergamotDB.class, name = "backup_code", since = @SQLVersion({ 3, 40, 0 })) public class ContactBackupCode implements Serializable { private static final long serialVersionUID = 1L; @SQLColumn(index = 1, name = "id", since = @SQLVersion({ 3,40, 0 })) @SQLPrimaryKey private UUID id; /** * The contact whom this backup code is for */ @SQLColumn(index = 2, name = "contact_id", since = @SQLVersion({ 3, 40, 0 })) @SQLForeignKey(references = Contact.class, on = "id", onDelete = Action.CASCADE, onUpdate = Action.RESTRICT, since = @SQLVersion({ 1, 0, 0 })) private UUID contactId; @SQLColumn(index = 3, name = "code", notNull = true, since = @SQLVersion({ 3, 40, 0 })) private String code; /** * When did we create this code */ @SQLColumn(index = 4, name = "created", since = @SQLVersion({ 3, 40, 0 })) private Timestamp created = new Timestamp(System.currentTimeMillis()); /** * When was this last updated */ @SQLColumn(index = 5, name = "updated", since = @SQLVersion({ 3, 40, 0 })) private Timestamp updated = null; /** * Was this code used */ @SQLColumn(index = 6, name = "used", since = @SQLVersion({ 3, 40, 0 })) private boolean used = false; /** * When was it used */ @SQLColumn(index = 7, name = "used_at", since = @SQLVersion({ 3, 40, 0 })) private Timestamp usedAt = null; public ContactBackupCode() { super(); } public ContactBackupCode(Contact contact) { this.id = Site.randomId(contact.getSiteId()); this.contactId = contact.getId(); this.created = new Timestamp(System.currentTimeMillis()); this.updated = null; this.used = false; this.usedAt = null; this.code = generateNewBackupCode(); } public UUID getContactId() { return contactId; } public void setContactId(UUID contactId) { this.contactId = contactId; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public Timestamp getCreated() { return created; } public void setCreated(Timestamp created) { this.created = created; } public Timestamp getUpdated() { return updated; } public void setUpdated(Timestamp updated) { this.updated = updated; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public boolean isUsed() { return used; } public void setUsed(boolean used) { this.used = used; } public Timestamp getUsedAt() { return usedAt; } public void setUsedAt(Timestamp usedAt) { this.usedAt = usedAt; } public Contact getContact() { if (this.getContactId() == null) return null; try (BergamotDB db = BergamotDB.connect()) { return db.getContact(this.getContactId()); } } public ContactBackupCode used() { this.used = true; this.usedAt = new Timestamp(System.currentTimeMillis()); this.updated = this.usedAt; return this; } public String toString() { return "BackupCode { code=" + this.code + ", used=" + this.used + "}"; } public static final String generateNewBackupCode() { byte[] key = new byte[5]; (new SecureRandom()).nextBytes(key); StringBuilder sb = new StringBuilder(); char[] token = (new Base32()).encodeToString(key).toLowerCase().toCharArray(); for (int i = 0; i < token.length; i++) { if (i > 0 && (i % 4) == 0) sb.append("-"); sb.append(token[i]); } return sb.toString(); } }