golang 命令行上的双因素身份验证。请参阅https://github.com/rsc/2fa

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang 命令行上的双因素身份验证。请参阅https://github.com/rsc/2fa相关的知识,希望对你有一定的参考价值。

// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// 2fa is a two-factor authentication agent.
//
// Usage:
//
//	2fa -add [-7] [-8] [-hotp] name
//	2fa -list
//	2fa [-clip] name
//
// “2fa -add name” adds a new key to the 2fa keychain with the given name.
// It prints a prompt to standard error and reads a two-factor key from standard input.
// Two-factor keys are short case-insensitive strings of letters A-Z and digits 2-7.
//
// By default the new key generates time-based (TOTP) authentication codes;
// the -hotp flag makes the new key generate counter-based (HOTP) codes instead.
//
// By default the new key generates 6-digit codes; the -7 and -8 flags select
// 7- and 8-digit codes instead.
//
// “2fa -list” lists the names of all the keys in the keychain.
//
// “2fa name” prints a two-factor authentication code from the key with the
// given name. If “-clip” is specified, 2fa also copies the code to the system
// clipboard.
//
// With no arguments, 2fa prints two-factor authentication codes from all
// known time-based keys.
//
// The default time-based authentication codes are derived from a hash of
// the key and the current time, so it is important that the system clock have
// at least one-minute accuracy.
//
// The keychain is stored unencrypted in the text file $HOME/.2fa.
//
// Example
//
// During GitHub 2FA setup, at the “Scan this barcode with your app” step,
// click the “enter this text code instead” link. A window pops up showing
// “your two-factor secret,” a short string of letters and digits.
//
// Add it to 2fa under the name github, typing the secret at the prompt:
//
//	$ 2fa -add github
//	2fa key for github: nzxxiidbebvwk6jb
//	$
//
// Then whenever GitHub prompts for a 2FA code, run 2fa to obtain one:
//
//	$ 2fa github
//	268346
//	$
//
// Or to type less:
//
//	$ 2fa
//	268346	github
//	$
//
package main

import (
	"bufio"
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base32"
	"encoding/binary"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/atotto/clipboard"
)

var (
	flagAdd  = flag.Bool("add", false, "add a key")
	flagList = flag.Bool("list", false, "list keys")
	flagHotp = flag.Bool("hotp", false, "add key as HOTP (counter-based) key")
	flag7    = flag.Bool("7", false, "generate 7-digit code")
	flag8    = flag.Bool("8", false, "generate 8-digit code")
	flagClip = flag.Bool("clip", false, "copy code to the clipboard")
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage:\n")
	fmt.Fprintf(os.Stderr, "\t2fa -add [-7] [-8] [-hotp] keyname\n")
	fmt.Fprintf(os.Stderr, "\t2fa -list\n")
	fmt.Fprintf(os.Stderr, "\t2fa [-clip] keyname\n")
	os.Exit(2)
}

func main() {
	log.SetPrefix("2fa: ")
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()

	k := readKeychain(filepath.Join(os.Getenv("HOME"), ".2fa"))

	if *flagList {
		if flag.NArg() != 0 {
			usage()
		}
		k.list()
		return
	}
	if flag.NArg() == 0 && !*flagAdd {
		if *flagClip {
			usage()
		}
		k.showAll()
		return
	}
	if flag.NArg() != 1 {
		usage()
	}
	name := flag.Arg(0)
	if strings.IndexFunc(name, unicode.IsSpace) >= 0 {
		log.Fatal("name must not contain spaces")
	}
	if *flagAdd {
		if *flagClip {
			usage()
		}
		k.add(name)
		return
	}
	k.show(name)
}

type Keychain struct {
	file string
	data []byte
	keys map[string]Key
}

type Key struct {
	raw    []byte
	digits int
	offset int // offset of counter
}

const counterLen = 20

func readKeychain(file string) *Keychain {
	c := &Keychain{
		file: file,
		keys: make(map[string]Key),
	}
	data, err := ioutil.ReadFile(file)
	if err != nil {
		if os.IsNotExist(err) {
			return c
		}
		log.Fatal(err)
	}
	c.data = data

	lines := bytes.SplitAfter(data, []byte("\n"))
	offset := 0
	for i, line := range lines {
		lineno := i + 1
		offset += len(line)
		f := bytes.Split(bytes.TrimSuffix(line, []byte("\n")), []byte(" "))
		if len(f) == 1 && len(f[0]) == 0 {
			continue
		}
		if len(f) >= 3 && len(f[1]) == 1 && '6' <= f[1][0] && f[1][0] <= '8' {
			var k Key
			name := string(f[0])
			k.digits = int(f[1][0] - '0')
			raw, err := decodeKey(string(f[2]))
			if err == nil {
				k.raw = raw
				if len(f) == 3 {
					c.keys[name] = k
					continue
				}
				if len(f) == 4 && len(f[3]) == counterLen {
					_, err := strconv.ParseUint(string(f[3]), 10, 64)
					if err == nil {
						// Valid counter.
						k.offset = offset - counterLen
						if line[len(line)-1] == '\n' {
							k.offset--
						}
						c.keys[name] = k
						continue
					}
				}
			}
		}
		log.Printf("%s:%d: malformed key", c.file, lineno)
	}
	return c
}

func (c *Keychain) list() {
	var names []string
	for name := range c.keys {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Println(name)
	}
}

func noSpace(r rune) rune {
	if unicode.IsSpace(r) {
		return -1
	}
	return r
}

func (c *Keychain) add(name string) {
	size := 6
	if *flag7 {
		size = 7
		if *flag8 {
			log.Fatalf("cannot use -7 and -8 together")
		}
	} else if *flag8 {
		size = 8
	}

	fmt.Fprintf(os.Stderr, "2fa key for %s: ", name)
	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
	if err != nil {
		log.Fatalf("error reading key: %v", err)
	}
	text = strings.Map(noSpace, text)
	text += strings.Repeat("=", -len(text)&7) // pad to 8 bytes
	if _, err := decodeKey(text); err != nil {
		log.Fatalf("invalid key: %v", err)
	}

	line := fmt.Sprintf("%s %d %s", name, size, text)
	if *flagHotp {
		line += " " + strings.Repeat("0", 20)
	}
	line += "\n"

	f, err := os.OpenFile(c.file, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
	if err != nil {
		log.Fatalf("opening keychain: %v", err)
	}
	f.Chmod(0600)

	if _, err := f.Write([]byte(line)); err != nil {
		log.Fatalf("adding key: %v", err)
	}
	if err := f.Close(); err != nil {
		log.Fatalf("adding key: %v", err)
	}
}

func (c *Keychain) code(name string) string {
	k, ok := c.keys[name]
	if !ok {
		log.Fatalf("no such key %q", name)
	}
	var code int
	if k.offset != 0 {
		n, err := strconv.ParseUint(string(c.data[k.offset:k.offset+counterLen]), 10, 64)
		if err != nil {
			log.Fatalf("malformed key counter for %q (%q)", name, c.data[k.offset:k.offset+counterLen])
		}
		n++
		code = hotp(k.raw, n, k.digits)
		f, err := os.OpenFile(c.file, os.O_RDWR, 0600)
		if err != nil {
			log.Fatalf("opening keychain: %v", err)
		}
		if _, err := f.WriteAt([]byte(fmt.Sprintf("%0*d", counterLen, n)), int64(k.offset)); err != nil {
			log.Fatalf("updating keychain: %v", err)
		}
		if err := f.Close(); err != nil {
			log.Fatalf("updating keychain: %v", err)
		}
	} else {
		// Time-based key.
		code = totp(k.raw, time.Now(), k.digits)
	}
	return fmt.Sprintf("%0*d", k.digits, code)
}

func (c *Keychain) show(name string) {
	code := c.code(name)
	if *flagClip {
		clipboard.WriteAll(code)
	}
	fmt.Printf("%s\n", code)
}

func (c *Keychain) showAll() {
	var names []string
	max := 0
	for name, k := range c.keys {
		names = append(names, name)
		if max < k.digits {
			max = k.digits
		}
	}
	sort.Strings(names)
	for _, name := range names {
		k := c.keys[name]
		code := strings.Repeat("-", k.digits)
		if k.offset == 0 {
			code = c.code(name)
		}
		fmt.Printf("%-*s\t%s\n", max, code, name)
	}
}

func decodeKey(key string) ([]byte, error) {
	return base32.StdEncoding.DecodeString(strings.ToUpper(key))
}

func hotp(key []byte, counter uint64, digits int) int {
	h := hmac.New(sha1.New, key)
	binary.Write(h, binary.BigEndian, counter)
	sum := h.Sum(nil)
	v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
	d := uint32(1)
	for i := 0; i < digits && i < 8; i++ {
		d *= 10
	}
	return int(v % d)
}

func totp(key []byte, t time.Time, digits int) int {
	return hotp(key, uint64(t.UnixNano())/30e9, digits)
}

检测 TableView JavaFX 行上的双击

【中文标题】检测 TableView JavaFX 行上的双击【英文标题】:Detect doubleclick on row of TableView JavaFX 【发布时间】:2014-12-21 04:54:57 【问题描述】:

我需要检测对TableView 的一行的双击。

如何监听该行任何部分的双击并获取该行的所有数据以将其打印到控制台?

【问题讨论】:

使用双击单元格的解决方案会起作用,因为您始终可以在单元格上调用getTableRow().getItem() 来获取该行的项目。更好的是直接在表格行中注册一个监听器(见答案)。 【参考方案1】:

我有类似的情况,没有检测到 TableView 上的鼠标双击事件。 最重要的是,样品工作得很好。但我的应用程序根本没有检测到双击事件。

但是我发现如果TableView处于可编辑状态,就无法检测到鼠标双击事件!!

如果 TableView 像这样可编辑,请检查您的应用程序。

tableView.setEditable( true );

如果这样,双击事件只会在选中的同一行上引发。

【讨论】:

【参考方案2】:

这对我有用:

table.setOnMouseClicked((MouseEvent event) -> 
            if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2)
                System.out.println(table.getSelectionModel().getSelectedItem());
            
        );
    

【讨论】:

【参考方案3】:
TableView<MyType> table = new TableView<>();

//...

table.setRowFactory( tv -> 
    TableRow<MyType> row = new TableRow<>();
    row.setOnMouseClicked(event -> 
        if (event.getClickCount() == 2 && (! row.isEmpty()) ) 
            MyType rowData = row.getItem();
            System.out.println(rowData);
        
    );
    return row ;
);

这是一个完整的工作示例:

import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class TableViewDoubleClickOnRow extends Application 

    @Override
    public void start(Stage primaryStage) 
        TableView<Item> table = new TableView<>();
        table.setRowFactory(tv -> 
            TableRow<Item> row = new TableRow<>();
            row.setOnMouseClicked(event -> 
                if (event.getClickCount() == 2 && (! row.isEmpty()) ) 
                    Item rowData = row.getItem();
                    System.out.println("Double click on: "+rowData.getName());
                
            );
            return row ;
        );
        table.getColumns().add(column("Item", Item::nameProperty));
        table.getColumns().add(column("Value", Item::valueProperty));

        Random rng = new Random();
        for (int i = 1 ; i <= 50 ; i++) 
            table.getItems().add(new Item("Item "+i, rng.nextInt(1000)));
        

        Scene scene = new Scene(table);
        primaryStage.setScene(scene);
        primaryStage.show();
    

    private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) 
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col ;
    

    public static class Item 
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty value = new SimpleIntegerProperty();

        public Item(String name, int value) 
            setName(name);
            setValue(value);
        

        public StringProperty nameProperty() 
            return name ;
        

        public final String getName() 
            return nameProperty().get();
        

        public final void setName(String name) 
            nameProperty().set(name);
        

        public IntegerProperty valueProperty() 
            return value ;
        

        public final int getValue() 
            return valueProperty().get();
        

        public final void setValue(int value) 
            valueProperty().set(value);
        
    

    public static void main(String[] args) 
        launch(args);
    

【讨论】:

@Aubin 我认为既然这个答案被接受并且有 80 多票赞成票,那么可以合理地假设如果它不适合你,那么你实现这个的方式就有问题。使用minimal reproducible example 发布您自己的问题。 如果我想改用细胞工厂怎么办?我已经有为每个字段使用PropertyValueFactory 的代码,如何扩展您的方法来监听双击?另外,我不明白你的行工厂的这一部分:你创建一个新的TableRow,在上面调用getItem,然后打印这个项目的名称?何时涉及实际数据? @Arno A PropertyValueFactory 用于cellValueFactory,而不是cellFactory。您的TableColumns 上始终需要cellValueFactory(请注意,表中的两列也都定义了cellValueFactorys)。如果需要,您可以在部分或全部列上另外定义cellFactorys;他们独立于此。不确定我是否理解您的最后一个问题;您所指的代码在鼠标处理程序中。因此,当鼠标双击该行时,处理程序会检索该行的值并显示其名称。 我刚刚明白ValueFactoryCellFactory 之间的区别,谢谢。至于我的第二个问题,我只是错过了上下文,我现在明白了。【参考方案4】:

这个答案已经过测试:

table.setOnMouseClicked( event -> 
   if( event.getClickCount() == 2 ) 
      System.out.println( table.getSelectionModel().getSelectedItem());
   );

table.getSelectionModel().getSelectedItem() 可以使用,因为我们双击。第一次单击选择移动,第二次执行此处理程序。

【讨论】:

【参考方案5】:

如果您使用SceneBuilder,您可以将您的表的OnMouseClicked 设置为handleRowSelect() 方法,如下所示:

MyType temp;
Date lastClickTime;
@FXML
private void handleRowSelect() 
    MyType row = myTableView.getSelectionModel().getSelectedItem();
    if (row == null) return;
    if(row != temp)
        temp = row;
        lastClickTime = new Date();
     else if(row == temp) 
        Date now = new Date();
        long diff = now.getTime() - lastClickTime.getTime();
        if (diff < 300) //another click registered in 300 millis
             System.out.println("Edit dialog");
         else 
            lastClickTime = new Date();
        
    

【讨论】:

您可以获取事件handleRowSelect(MouseEvent event) 来检测与@Abdul.Moqueet 回答中的主要鼠标按钮的双击【参考方案6】:

例子:

table.setOnMousePressed(new EventHandler<MouseEvent>() 
    @Override 
    public void handle(MouseEvent event) 
        if (event.isPrimaryButtonDown() && event.getClickCount() == 2) 
            System.out.println(table.getSelectionModel().getSelectedItem());                   
        
    
);

如果您使用自定义选择模型,则可以从事件中获取行,例如:

table.setOnMousePressed(new EventHandler<MouseEvent>() 
    @Override 
    public void handle(MouseEvent event) 
        if (event.isPrimaryButtonDown() && event.getClickCount() == 2) 
            Node node = ((Node) event.getTarget()).getParent();
            TableRow row;
            if (node instanceof TableRow) 
                row = (TableRow) node;
             else 
                // clicking on text part
                row = (TableRow) node.getParent();
            
            System.out.println(row.getItem());
        
    
);

【讨论】:

虽然这行得通(因为双击的第一次单击选择了行),选择在语义上是不同的,将鼠标处理程序附加到行。如果您出于某种原因需要使用自定义选择模型,则可能会破坏此代码。 @James_D 在问题中没有写下自定义选择模型案例,但也针对此案例调整了答案。 这依赖于特定的节点层次结构(这是未记录的,并且故意如此)。如果在 JavaFX 的未来版本中表视图布局机制的实现发生了变化怎么办?只需在创建时将侦听器直接附加到表行即可。 你也有点误解了我的意思。问题不在于您的代码是否支持自定义选择模型,而实际上“选择”和“注册鼠标侦听器”是两个独立的东西。使用“选择了什么”作为一种机制来确定单击鼠标的节点会在 API 的两个部分之间引入以前不存在的耦合(依赖性)。 谢谢,但我必须使用 event.getButton().equals(MouseButton.PRIMARY) 而不是 event.isPrimaryButtonDown() 才能让我的工作。

以上是关于golang 命令行上的双因素身份验证。请参阅https://github.com/rsc/2fa的主要内容,如果未能解决你的问题,请参考以下文章

有人知道 Symfony 3 构建的应用程序的双因素身份验证 API 吗?

Reddit系统被黑:基于短信验证码的双因素认证并不安全

检测 TableView JavaFX 行上的双击

Vulnerabilities in multi-factor authentication:多因素身份验证漏洞

GitHub 要求所有账户开启双因素身份验证

System.Net.WebException:错误:TrustFailure(身份验证失败,请参阅内部异常。)